From af2576384ea5a961ae2a228276e293967deee7c2 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 25 May 2022 15:53:44 +0200 Subject: [PATCH 01/15] Updated cloud-access-swift dependency, which adds GCM support for content encryption/decryption --- .../xcshareddata/swiftpm/Package.resolved | 24 +++++++++---------- CryptomatorCommon/Package.swift | 2 +- .../Manager/VaultDBManager.swift | 9 +++---- .../Manager/VaultDBCacheTests.swift | 2 +- .../Manager/VaultManagerTests.swift | 14 +++++------ .../AddLocalVaultViewModelTestCase.swift | 7 +++--- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f81754ea3..cbacd91b1 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/openid/AppAuth-iOS.git", "state": { "branch": null, - "revision": "01131d68346c8ae552961c768d583c715fbe1410", - "version": "1.4.0" + "revision": "33660c271c961f8ce1084cc13f2ea8195e864f7d", + "version": "1.5.0" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/cryptomator/cloud-access-swift.git", "state": { "branch": null, - "revision": "0bdd40641ef409e46dab8e70be7ef3d9cc76309e", - "version": "1.2.3" + "revision": "f8aa06dbc245368aee16ff3e4047ab0cb881a81d", + "version": "1.3.0" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/cryptomator/cryptolib-swift.git", "state": { "branch": null, - "revision": "ea363662f52e3cdddd5c319da6894246b3bdf565", - "version": "1.0.2" + "revision": "6e5dbea6e05742ad82a074bf7ee8c3305d92fbae", + "version": "1.1.0" } }, { @@ -87,8 +87,8 @@ "repositoryURL": "https://github.com/google/gtm-session-fetcher.git", "state": { "branch": null, - "revision": "bc6a19702ac76ac4e488b68148710eb815f9bc56", - "version": "1.7.0" + "revision": "4e9bbf2808b8fee444e84a48f5f3c12641987d3e", + "version": "1.7.2" } }, { @@ -96,8 +96,8 @@ "repositoryURL": "https://github.com/google/GTMAppAuth.git", "state": { "branch": null, - "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", - "version": "1.2.2" + "revision": "e803d09da0147fbf1bbb30e126c47ff43254e057", + "version": "1.2.3" } }, { @@ -114,8 +114,8 @@ "repositoryURL": "https://github.com/AzureAD/microsoft-authentication-library-for-objc.git", "state": { "branch": null, - "revision": "2825c3f72bf51f8be43df7177fe1c66370be2d0e", - "version": "1.2.0" + "revision": "e4202b9175d7b5566845b60e0ad233ed72b2783c", + "version": "1.2.1" } }, { diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index 12f906d37..288cf6740 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -26,7 +26,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.2.0")), + .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.3.0")), .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMinor(from: "3.7.0")) ], targets: [ diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index 4294880ac..659fecccc 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -74,7 +74,8 @@ public class VaultDBManager: VaultManager { */ public func createNewVault(withVaultUID vaultUID: String, delegateAccountUID: String, vaultPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise { let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) - let vaultConfig = VaultConfig.createNew(format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let cipherCombo = CryptorScheme.sivCtrMac + let vaultConfig = VaultConfig.createNew(format: 8, cipherCombo: cipherCombo, shorteningThreshold: 220) let masterkey: Masterkey let provider: LocalizedCloudProviderDecorator let vaultConfigToken: Data @@ -96,7 +97,7 @@ public class VaultDBManager: VaultManager { return try self.uploadVaultConfigToken(vaultConfigToken, vaultPath: vaultPath, provider: provider, tmpDirURL: tmpDirURL) }.then { vaultConfigMetadata -> Promise in cachedVault.vaultConfigLastModifiedDate = vaultConfigMetadata.lastModifiedDate - return try self.createVaultFolderStructure(masterkey: masterkey, vaultPath: vaultPath, provider: provider) + return try self.createVaultFolderStructure(masterkey: masterkey, cipherCombo: cipherCombo, vaultPath: vaultPath, provider: provider) }.then { _ -> Promise in let unverifiedVaultConfig = try UnverifiedVaultConfig(token: vaultConfigToken) _ = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: provider.delegate) @@ -134,8 +135,8 @@ public class VaultDBManager: VaultManager { return provider.uploadFile(from: localVaultConfigURL, to: vaultConfigCloudPath, replaceExisting: false) } - private func createVaultFolderStructure(masterkey: Masterkey, vaultPath: CloudPath, provider: CloudProvider) throws -> Promise { - let cryptor = Cryptor(masterkey: masterkey) + private func createVaultFolderStructure(masterkey: Masterkey, cipherCombo: CryptorScheme, vaultPath: CloudPath, provider: CloudProvider) throws -> Promise { + let cryptor = Cryptor(masterkey: masterkey, scheme: cipherCombo) let rootDirPath = try VaultDBManager.getRootDirectoryPath(for: cryptor, vaultPath: vaultPath) let dPath = vaultPath.appendingPathComponent("d") return provider.createFolder(at: dPath).then { _ -> Promise in diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultDBCacheTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultDBCacheTests.swift index f3a06c6f2..6b63efadb 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultDBCacheTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultDBCacheTests.swift @@ -28,7 +28,7 @@ class VaultDBCacheTests: XCTestCase { private let updatedMasterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x56, count: 32), macMasterKey: [UInt8](repeating: 0x78, count: 32)) private var vaultConfigData: Data! private var updatedVaultConfigData: Data! - private let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + private let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) private var defaultCachedVault: CachedVault! private let updatedMasterkeyFileLastModifiedDate = Date(timeIntervalSince1970: 100) private let updatedVaultConfigLastModifiedDate = Date(timeIntervalSince1970: 200) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift index 3ce635fba..3bd7ef392 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift @@ -149,7 +149,7 @@ class VaultManagerTests: XCTestCase { } XCTAssertEqual(uploadedVaultConfigToken, savedVaultConfigToken) let vaultConfig = try UnverifiedVaultConfig(token: savedVaultConfigToken) - XCTAssertEqual(.sivCTRMAC, vaultConfig.allegedCipherCombo) + XCTAssertEqual(.sivCtrMac, vaultConfig.allegedCipherCombo) XCTAssertEqual(8, vaultConfig.allegedFormat) }.catch { error in XCTFail("Promise failed with error: \(error)") @@ -177,7 +177,7 @@ class VaultManagerTests: XCTestCase { let vaultConfigPath = vaultPath.appendingPathComponent("vault.cryptomator") let vaultConfigID = "ABB9F673-F3E8-41A7-A43B-D29F5DA65068" - let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) let token = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) cloudProviderMock.filesToDownload[vaultConfigPath.path] = token cloudProviderMock.cloudMetadata[vaultConfigPath.path] = CloudItemMetadata(name: vaultConfigPath.lastPathComponent, cloudPath: vaultConfigPath, itemType: .file, lastModifiedDate: vaultConfigLastModifiedDate, size: nil) @@ -207,7 +207,7 @@ class VaultManagerTests: XCTestCase { let savedVaultConfig = try VaultConfig.load(token: savedVaultConfigToken, rawKey: masterkey.rawKey) XCTAssertEqual(vaultConfigID, savedVaultConfig.id) XCTAssertEqual(220, savedVaultConfig.shorteningThreshold) - XCTAssertEqual(.sivCTRMAC, savedVaultConfig.cipherCombo) + XCTAssertEqual(.sivCtrMac, savedVaultConfig.cipherCombo) XCTAssertEqual(8, savedVaultConfig.format) }.catch { error in XCTFail("Promise failed with error: \(error)") @@ -352,7 +352,7 @@ class VaultManagerTests: XCTestCase { let vaultConfigPath = vaultPath.appendingPathComponent("vault.cryptomator") let vaultConfigID = "ABB9F673-F3E8-41A7-A43B-D29F5DA65068" - let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) let token = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) cloudProviderMock.filesToDownload[vaultConfigPath.path] = token cloudProviderMock.cloudMetadata[vaultConfigPath.path] = CloudItemMetadata(name: vaultConfigPath.lastPathComponent, cloudPath: vaultConfigPath, itemType: .file, lastModifiedDate: vaultConfigLastModifiedDate, size: nil) @@ -529,7 +529,7 @@ class VaultManagerTests: XCTestCase { let masterkeyFileData = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 999, passphrase: oldPassphrase, pepper: [UInt8](), scryptCostParam: 2) let vaultConfigID = "ABB9F673-F3E8-41A7-A43B-D29F5DA65068" - let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) let token = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) let oldLastUpToDateCheck = Date(timeIntervalSince1970: 0) @@ -563,7 +563,7 @@ class VaultManagerTests: XCTestCase { passwordManagerMock.savedPasswords[vaultUID] = oldPassphrase let vaultConfigID = "ABB9F673-F3E8-41A7-A43B-D29F5DA65068" - let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let vaultConfig = VaultConfig(id: vaultConfigID, format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) let token = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) let oldLastUpToDateCheck = Date(timeIntervalSince1970: 0) @@ -604,7 +604,7 @@ class VaultManagerTests: XCTestCase { var vaultConfigToken: Data? var masterkeyFileVaultVersion = vaultVersion if !isLegacyVault(vaultVersion: vaultVersion) { - let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: vaultVersion, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: vaultVersion, cipherCombo: .sivCtrMac, shorteningThreshold: 220) vaultConfigToken = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) masterkeyFileVaultVersion = 999 } diff --git a/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift b/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift index 1d0116c15..0b9e5083a 100644 --- a/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift +++ b/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift @@ -27,9 +27,8 @@ class AddLocalVaultViewModelTestCase: XCTestCase { func createVault(at url: URL) throws { let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) - let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) - - let cryptor = Cryptor(masterkey: masterkey) + let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCtrMac, shorteningThreshold: 220) + let cryptor = Cryptor(masterkey: masterkey, scheme: .sivCtrMac) let rootDirPath = try getRootDirectoryURL(for: cryptor, vaultURL: url) try FileManager.default.createDirectory(at: rootDirPath, withIntermediateDirectories: true, attributes: nil) let masterkeyData = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 999, passphrase: "password", scryptCostParam: 2) @@ -42,7 +41,7 @@ class AddLocalVaultViewModelTestCase: XCTestCase { func createLegacyVault(at url: URL) throws { let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) - let cryptor = Cryptor(masterkey: masterkey) + let cryptor = Cryptor(masterkey: masterkey, scheme: .sivCtrMac) let rootDirPath = try getRootDirectoryURL(for: cryptor, vaultURL: url) try FileManager.default.createDirectory(at: rootDirPath, withIntermediateDirectories: true, attributes: nil) let masterkeyData = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "password", scryptCostParam: 2) From 9ab0a399842d9ba84b12188f817e7a6032e4c2a0 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 25 May 2022 16:08:00 +0200 Subject: [PATCH 02/15] Fixed tests --- .../CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift index 3bd7ef392..8503c8052 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift @@ -149,7 +149,7 @@ class VaultManagerTests: XCTestCase { } XCTAssertEqual(uploadedVaultConfigToken, savedVaultConfigToken) let vaultConfig = try UnverifiedVaultConfig(token: savedVaultConfigToken) - XCTAssertEqual(.sivCtrMac, vaultConfig.allegedCipherCombo) + XCTAssertEqual("SIV_CTRMAC", vaultConfig.allegedCipherCombo) XCTAssertEqual(8, vaultConfig.allegedFormat) }.catch { error in XCTFail("Promise failed with error: \(error)") From d98a81beb16fed7f34b3f4307856b56e258430af Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Mon, 30 May 2022 12:49:25 +0200 Subject: [PATCH 03/15] Feature: shortcuts (#222) * Update matchfile to include intents * Use NSFileCoordinator with purposeIdentifier to prevent file access errors * Add "Save File" and "Get Folder" intents * Update SharedResources/en.lproj/Localizable.strings [ci skip] Co-authored-by: Tobias Hagemann * Revert unnecessary changes to the project file * Simplified Xcode project file and added shared scheme for CryptomatorIntents * Check the full version status when executing the file import via XPC * Add detailed error messages for the `GetFolderIntent` * Update SharedResources/en.lproj/Localizable.strings [ci skip] Co-authored-by: Tobias Hagemann Co-authored-by: Tobias Hagemann --- Cryptomator.xcodeproj/project.pbxproj | 205 ++++++- .../xcschemes/CryptomatorIntents.xcscheme | 97 +++ Cryptomator/Info.plist | 9 +- .../FileProviderXPC/FileImporting.swift | 65 ++ .../FileProviderAdapter.swift | 100 +++- .../FileProviderAdapterManager.swift | 9 +- .../FileImportingServiceSource.swift | 108 ++++ .../FileImportingServiceSourceTests.swift | 129 ++++ ...eProviderAdapterCreateDirectoryTests.swift | 4 +- ...roviderAdapterGetItemIdentifierTests.swift | 112 ++++ ...eProviderAdapterImportDirectoryTests.swift | 2 +- ...leProviderAdapterImportDocumentTests.swift | 2 +- .../FileProviderAdapterTestCase.swift | 4 +- .../FileProviderAdapterManagerTests.swift | 3 +- .../Mocks/FileProviderAdapterTypeMock.swift | 20 + .../CryptomatorIntents.entitlements | 10 + .../GetFolderIntentHandler.swift | 142 +++++ .../GetFolderIntentHandlerError.swift | 24 + CryptomatorIntents/Info.plist | 25 + CryptomatorIntents/IntentHandler.swift | 21 + CryptomatorIntents/Intents.intentdefinition | 557 ++++++++++++++++++ .../SaveFileIntentHandler.swift | 167 ++++++ .../FileProviderExtension.swift | 1 + SharedResources/en.lproj/Localizable.strings | 9 + fastlane/Matchfile | 2 +- 25 files changed, 1807 insertions(+), 20 deletions(-) create mode 100644 Cryptomator.xcodeproj/xcshareddata/xcschemes/CryptomatorIntents.xcscheme create mode 100644 CryptomatorCommon/Sources/CryptomatorCommonCore/FileProviderXPC/FileImporting.swift create mode 100644 CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift create mode 100644 CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift create mode 100644 CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterGetItemIdentifierTests.swift create mode 100644 CryptomatorIntents/CryptomatorIntents.entitlements create mode 100644 CryptomatorIntents/GetFolderIntentHandler.swift create mode 100644 CryptomatorIntents/GetFolderIntentHandlerError.swift create mode 100644 CryptomatorIntents/Info.plist create mode 100644 CryptomatorIntents/IntentHandler.swift create mode 100644 CryptomatorIntents/Intents.intentdefinition create mode 100644 CryptomatorIntents/SaveFileIntentHandler.swift diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 7dd221244..1d0d5ff92 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ 4A1673EA270C77CC0075C724 /* DocumentStorageURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1673E9270C77CC0075C724 /* DocumentStorageURLProvider.swift */; }; 4A1673ED270DE4600075C724 /* libCryptomatorFileProvider.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 740375D72587AE7A0023FF53 /* libCryptomatorFileProvider.a */; }; 4A1673F1270E2E830075C724 /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1673F0270E2E830075C724 /* SettingsViewModelTests.swift */; }; + 4A1A7AC528326554008EEC84 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 742679F926A56B33004C61BC /* Localizable.strings */; }; + 4A1A7AC628327419008EEC84 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AE97DB324572E4A00452814 /* Assets.xcassets */; }; 4A1C6D58274CE5BF00B41FFF /* LoadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1C6D57274CE5BF00B41FFF /* LoadingCell.swift */; }; 4A1C6D5A274D225500B41FFF /* PurchaseViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1C6D59274D225500B41FFF /* PurchaseViewModelTests.swift */; }; 4A1C6D5C274D292200B41FFF /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 74F5DC1726D928A100AFE989 /* Configuration.storekit */; }; @@ -78,6 +80,9 @@ 4A2FD07925B5D98B008565C8 /* CloudCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2FD07825B5D98B008565C8 /* CloudCell.swift */; }; 4A2FD08225B5E2BA008565C8 /* VaultInstalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2FD08125B5E2BA008565C8 /* VaultInstalling.swift */; }; 4A2FD08B25B5E437008565C8 /* OpenExistingVaultCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2FD08A25B5E437008565C8 /* OpenExistingVaultCoordinator.swift */; }; + 4A33092B282EBF9900876A3E /* SaveFileIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A33092A282EBF9900876A3E /* SaveFileIntentHandler.swift */; }; + 4A33092D282EC23400876A3E /* CryptomatorCommonCore in Frameworks */ = {isa = PBXBuildFile; productRef = 4A33092C282EC23400876A3E /* CryptomatorCommonCore */; }; + 4A330931282EC7CE00876A3E /* FileImportingServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A330930282EC7CE00876A3E /* FileImportingServiceSource.swift */; }; 4A3C5DD4272AF98700EB7C7A /* MaintenanceManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3C5DD3272AF98700EB7C7A /* MaintenanceManagerMock.swift */; }; 4A3C5DD8272BF39000EB7C7A /* ChangePasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3C5DD7272BF39000EB7C7A /* ChangePasswordViewController.swift */; }; 4A3C5DDA272BF52600EB7C7A /* TextFieldCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3C5DD9272BF52600EB7C7A /* TextFieldCellViewModel.swift */; }; @@ -194,6 +199,9 @@ 4A80407D27692A0100D7D999 /* FileProviderEnumeratorSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A80407C27692A0100D7D999 /* FileProviderEnumeratorSnapshotMock.swift */; }; 4A80408027694C6600D7D999 /* VaultUnlockingServiceSourceSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A80407F27694C6600D7D999 /* VaultUnlockingServiceSourceSnapshotMock.swift */; }; 4A804082276952C300D7D999 /* FileProviderCoordinatorSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A804081276952C300D7D999 /* FileProviderCoordinatorSnapshotMock.swift */; }; + 4A82C88A2832443D00548F87 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; + 4A85ECBE283CBF4700E23024 /* FileImportingServiceSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A85ECBD283CBF4700E23024 /* FileImportingServiceSourceTests.swift */; }; + 4A85ECC4283D0DED00E23024 /* GetFolderIntentHandlerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A85ECC3283D0DED00E23024 /* GetFolderIntentHandlerError.swift */; }; 4A88816427440CE300F7AA6E /* BaseUITableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A88816327440CE300F7AA6E /* BaseUITableViewController.swift */; }; 4A8CF8C127E906D7004CE880 /* FileProviderAdapterImportDirectoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8CF8C027E906D7004CE880 /* FileProviderAdapterImportDirectoryTests.swift */; }; 4A8D05D625C5CBE10082C5F7 /* AddVaultSuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8D05D525C5CBE10082C5F7 /* AddVaultSuccessViewController.swift */; }; @@ -219,6 +227,7 @@ 4A9D123F261E1DD400A670E2 /* WebDAVAuthenticating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9D123E261E1DD400A670E2 /* WebDAVAuthenticating.swift */; }; 4A9D1247261E227600A670E2 /* WebDAVAuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9D1246261E227600A670E2 /* WebDAVAuthenticationCoordinator.swift */; }; 4A9D124F261F071F00A670E2 /* WebDAVAuthenticator+VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9D124E261F071F00A670E2 /* WebDAVAuthenticator+VC.swift */; }; + 4AA08E7F28379C6100972A15 /* GetFolderIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA08E7E28379C6100972A15 /* GetFolderIntentHandler.swift */; }; 4AA22BFB261CA69F00A17486 /* WebDAVAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA22BFA261CA69F00A17486 /* WebDAVAuthenticationViewController.swift */; }; 4AA22C16261CA8D800A17486 /* URLFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA22C15261CA8D800A17486 /* URLFieldCell.swift */; }; 4AA22C1E261CA94700A17486 /* UsernameFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA22C1D261CA94700A17486 /* UsernameFieldCell.swift */; }; @@ -237,6 +246,7 @@ 4AA8614825C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA8614725C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift */; }; 4AA8615125C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */; }; 4AAD444727E26D1800D16707 /* UploadTaskManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAD444627E26D1800D16707 /* UploadTaskManagerMock.swift */; }; + 4AB05A4D283BA362001702D5 /* FileProviderAdapterGetItemIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB05A4C283BA362001702D5 /* FileProviderAdapterGetItemIdentifierTests.swift */; }; 4AB1C325265CE69700DC7A49 /* DownloadTaskExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB1C324265CE69700DC7A49 /* DownloadTaskExecutorTests.swift */; }; 4AB1C33A265E9D8600DC7A49 /* UploadTaskExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB1C339265E9D8600DC7A49 /* UploadTaskExecutorTests.swift */; }; 4AB1C33C265E9DBC00DC7A49 /* CloudTaskExecutorTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB1C33B265E9DBC00DC7A49 /* CloudTaskExecutorTestCase.swift */; }; @@ -263,6 +273,7 @@ 4AB8539826BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539726BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift */; }; 4AB8539C26BA8A8200555F00 /* VaultPasswordVerifying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539B26BA8A8200555F00 /* VaultPasswordVerifying.swift */; }; 4AB8539E26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539D26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift */; }; + 4ABB4627282EBE4B00968C5E /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */; }; 4ABC08D7250D1EB600E3CEDC /* DeletionTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABC08D6250D1EB600E3CEDC /* DeletionTaskManagerTests.swift */; }; 4ABCF3522726D24800A7FBB7 /* MoveVaultViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCF3512726D24800A7FBB7 /* MoveVaultViewModelTests.swift */; }; 4AC005F127C3D80B006FFE87 /* PremiumManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC005F027C3D80B006FFE87 /* PremiumManager.swift */; }; @@ -271,6 +282,9 @@ 4AC1157827F5BEFD0023F51B /* Promise+AllIgnoringResultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC1157727F5BEFD0023F51B /* Promise+AllIgnoringResultsTests.swift */; }; 4AC86270273598CC00E15BA5 /* UIViewController+ProgressHUDError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC8626F273598CC00E15BA5 /* UIViewController+ProgressHUDError.swift */; }; 4AD0F61C24AF203F0026B765 /* FileProvider+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F61B24AF203F0026B765 /* FileProvider+Actions.swift */; }; + 4AD3D7D6282EBDE7008188CD /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AD3D7D5282EBDE7008188CD /* Intents.framework */; }; + 4AD3D7D9282EBDE7008188CD /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD3D7D8282EBDE7008188CD /* IntentHandler.swift */; }; + 4AD3D7DD282EBDE7008188CD /* CryptomatorIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4AD3D7D4282EBDE7008188CD /* CryptomatorIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4ADBD35827284BAB00B19B5C /* MoveVaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADBD35727284BAB00B19B5C /* MoveVaultViewController.swift */; }; 4ADC66BF27A44558002E6CC7 /* XCTestCase+Promises.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADC66BE27A44557002E6CC7 /* XCTestCase+Promises.swift */; }; 4ADC66C127A7F426002E6CC7 /* UnlockMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADC66C027A7F426002E6CC7 /* UnlockMonitor.swift */; }; @@ -425,6 +439,13 @@ remoteGlobalIDString = 740375D62587AE7A0023FF53; remoteInfo = CryptomatorFileProvider; }; + 4AD3D7DB282EBDE7008188CD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4A5E5B212453119100BD6298 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4AD3D7D3282EBDE7008188CD; + remoteInfo = CryptomatorIntents; + }; 4AE97DBE24572E4A00452814 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4A5E5B212453119100BD6298 /* Project object */; @@ -479,6 +500,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 4AD3D7DD282EBDE7008188CD /* CryptomatorIntents.appex in Embed App Extensions */, 4AEE468F25263B2E0045DA9F /* FileProviderExtension.appex in Embed App Extensions */, 4AEE469225263B2E0045DA9F /* FileProviderExtensionUI.appex in Embed App Extensions */, ); @@ -570,6 +592,8 @@ 4A2FD07825B5D98B008565C8 /* CloudCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudCell.swift; sourceTree = ""; }; 4A2FD08125B5E2BA008565C8 /* VaultInstalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultInstalling.swift; sourceTree = ""; }; 4A2FD08A25B5E437008565C8 /* OpenExistingVaultCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultCoordinator.swift; sourceTree = ""; }; + 4A33092A282EBF9900876A3E /* SaveFileIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFileIntentHandler.swift; sourceTree = ""; }; + 4A330930282EC7CE00876A3E /* FileImportingServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportingServiceSource.swift; sourceTree = ""; }; 4A3C5DD3272AF98700EB7C7A /* MaintenanceManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaintenanceManagerMock.swift; sourceTree = ""; }; 4A3C5DD7272BF39000EB7C7A /* ChangePasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordViewController.swift; sourceTree = ""; }; 4A3C5DD9272BF52600EB7C7A /* TextFieldCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCellViewModel.swift; sourceTree = ""; }; @@ -674,6 +698,7 @@ 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReparentTaskManagerTests.swift; sourceTree = ""; }; 4A74DBB0282132EC00A332C4 /* FileProviderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAction.swift; sourceTree = ""; }; 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLegacyVaultPasswordViewModel.swift; sourceTree = ""; }; + 4A753FBB2832A371006A9C3F /* CryptomatorIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CryptomatorIntents.entitlements; sourceTree = ""; }; 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItemTests.swift; sourceTree = ""; }; 4A797F9524AC9936007DDBE1 /* CustomCloudProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCloudProviderMock.swift; sourceTree = ""; }; 4A797F9724AC9A1B007DDBE1 /* CustomCloudProviderMockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCloudProviderMockTests.swift; sourceTree = ""; }; @@ -687,6 +712,8 @@ 4A80407C27692A0100D7D999 /* FileProviderEnumeratorSnapshotMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderEnumeratorSnapshotMock.swift; sourceTree = ""; }; 4A80407F27694C6600D7D999 /* VaultUnlockingServiceSourceSnapshotMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultUnlockingServiceSourceSnapshotMock.swift; sourceTree = ""; }; 4A804081276952C300D7D999 /* FileProviderCoordinatorSnapshotMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderCoordinatorSnapshotMock.swift; sourceTree = ""; }; + 4A85ECBD283CBF4700E23024 /* FileImportingServiceSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportingServiceSourceTests.swift; sourceTree = ""; }; + 4A85ECC3283D0DED00E23024 /* GetFolderIntentHandlerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetFolderIntentHandlerError.swift; sourceTree = ""; }; 4A88816327440CE300F7AA6E /* BaseUITableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITableViewController.swift; sourceTree = ""; }; 4A8CF8C027E906D7004CE880 /* FileProviderAdapterImportDirectoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAdapterImportDirectoryTests.swift; sourceTree = ""; }; 4A8D05D525C5CBE10082C5F7 /* AddVaultSuccessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddVaultSuccessViewController.swift; sourceTree = ""; }; @@ -712,6 +739,7 @@ 4A9D124E261F071F00A670E2 /* WebDAVAuthenticator+VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebDAVAuthenticator+VC.swift"; sourceTree = ""; }; 4A9FCB0B251A02A3002A8B41 /* FileProviderExtensionUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExtensionUI.entitlements; sourceTree = ""; }; 4A9FCB0C251A02AA002A8B41 /* Cryptomator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Cryptomator.entitlements; sourceTree = ""; }; + 4AA08E7E28379C6100972A15 /* GetFolderIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetFolderIntentHandler.swift; sourceTree = ""; }; 4AA22BFA261CA69F00A17486 /* WebDAVAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebDAVAuthenticationViewController.swift; sourceTree = ""; }; 4AA22C15261CA8D800A17486 /* URLFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLFieldCell.swift; sourceTree = ""; }; 4AA22C1D261CA94700A17486 /* UsernameFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameFieldCell.swift; sourceTree = ""; }; @@ -735,6 +763,7 @@ 4AA8614725C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultChooseFolderViewController.swift; sourceTree = ""; }; 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultPasswordViewController.swift; sourceTree = ""; }; 4AAD444627E26D1800D16707 /* UploadTaskManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTaskManagerMock.swift; sourceTree = ""; }; + 4AB05A4C283BA362001702D5 /* FileProviderAdapterGetItemIdentifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAdapterGetItemIdentifierTests.swift; sourceTree = ""; }; 4AB1C324265CE69700DC7A49 /* DownloadTaskExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskExecutorTests.swift; sourceTree = ""; }; 4AB1C339265E9D8600DC7A49 /* UploadTaskExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTaskExecutorTests.swift; sourceTree = ""; }; 4AB1C33B265E9DBC00DC7A49 /* CloudTaskExecutorTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudTaskExecutorTestCase.swift; sourceTree = ""; }; @@ -761,6 +790,7 @@ 4AB8539726BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailUnlockVaultViewModel.swift; sourceTree = ""; }; 4AB8539B26BA8A8200555F00 /* VaultPasswordVerifying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultPasswordVerifying.swift; sourceTree = ""; }; 4AB8539D26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailUnlockCoordinator.swift; sourceTree = ""; }; + 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; 4ABC08D6250D1EB600E3CEDC /* DeletionTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletionTaskManagerTests.swift; sourceTree = ""; }; 4ABCF3512726D24800A7FBB7 /* MoveVaultViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveVaultViewModelTests.swift; sourceTree = ""; }; 4AC005F027C3D80B006FFE87 /* PremiumManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumManager.swift; sourceTree = ""; }; @@ -769,6 +799,10 @@ 4AC1157727F5BEFD0023F51B /* Promise+AllIgnoringResultsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+AllIgnoringResultsTests.swift"; sourceTree = ""; }; 4AC8626F273598CC00E15BA5 /* UIViewController+ProgressHUDError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+ProgressHUDError.swift"; sourceTree = ""; }; 4AD0F61B24AF203F0026B765 /* FileProvider+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProvider+Actions.swift"; sourceTree = ""; }; + 4AD3D7D4282EBDE7008188CD /* CryptomatorIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CryptomatorIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AD3D7D5282EBDE7008188CD /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; + 4AD3D7D8282EBDE7008188CD /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; + 4AD3D7DA282EBDE7008188CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4ADBD35727284BAB00B19B5C /* MoveVaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveVaultViewController.swift; sourceTree = ""; }; 4ADC66BE27A44557002E6CC7 /* XCTestCase+Promises.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Promises.swift"; sourceTree = ""; }; 4ADC66C027A7F426002E6CC7 /* UnlockMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnlockMonitor.swift; sourceTree = ""; }; @@ -964,6 +998,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4AD3D7D1282EBDE7008188CD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AD3D7D6282EBDE7008188CD /* Intents.framework in Frameworks */, + 4A33092D282EC23400876A3E /* CryptomatorCommonCore in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4AE97DA524572E4900452814 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1070,6 +1113,7 @@ children = ( 4A2245D424A5E16300DBA437 /* Info.plist */, 4AFE6AA72514B65800A4A315 /* CloudPath+NameCollision.swift */, + 4A85ECBD283CBF4700E23024 /* FileImportingServiceSourceTests.swift */, 4AB6A88F278E1E5D0016B01E /* FileProviderAdapterManagerTests.swift */, 4A1673E7270C675B0075C724 /* FileProviderCacheManagerTests.swift */, 4AEECD38279EB1EB00C6E2B5 /* FileProviderEnumeratorTests.swift */, @@ -1211,6 +1255,7 @@ 4A51E5FD261C9A7000CC8C9B /* CryptomatorCommonHostedTests */, 740375D82587AE7B0023FF53 /* CryptomatorFileProvider */, 4A2245D124A5E16300DBA437 /* CryptomatorFileProviderTests */, + 4AD3D7D7282EBDE7008188CD /* CryptomatorIntents */, 4AE97DC024572E4A00452814 /* CryptomatorTests */, 4AA621D7249A6A8400A0BCBD /* FileProviderExtension */, 4AA621E5249A6A8400A0BCBD /* FileProviderExtensionUI */, @@ -1233,6 +1278,7 @@ 740375D72587AE7A0023FF53 /* libCryptomatorFileProvider.a */, 4A51E5FC261C9A7000CC8C9B /* CryptomatorCommonHostedTests.xctest */, 4A136121276767D60077EB7F /* Snapshots.xctest */, + 4AD3D7D4282EBDE7008188CD /* CryptomatorIntents.appex */, ); name = Products; sourceTree = ""; @@ -1411,6 +1457,7 @@ 4A828287252617D600A4EAD4 /* Frameworks */ = { isa = PBXGroup; children = ( + 4AD3D7D5282EBDE7008188CD /* Intents.framework */, ); name = Frameworks; sourceTree = ""; @@ -1422,6 +1469,7 @@ 4A24822C266F85FD002D9F59 /* FileProviderAdapterDeleteItemTests.swift */, 4A24822E266FACF1002D9F59 /* FileProviderAdapterEnumerateItemTests.swift */, 4A8F149F266A2FD500ADBCE4 /* FileProviderAdapterGetItemTests.swift */, + 4AB05A4C283BA362001702D5 /* FileProviderAdapterGetItemIdentifierTests.swift */, 4A248220266B8D37002D9F59 /* FileProviderAdapterImportDocumentTests.swift */, 4A24822A266E362C002D9F59 /* FileProviderAdapterMoveItemTests.swift */, 4AEECD32279EAACA00C6E2B5 /* FileProviderAdapterSetFavoriteRankTests.swift */, @@ -1486,6 +1534,7 @@ isa = PBXGroup; children = ( 4AA782D6282A7779001A71E3 /* CacheManagingServiceSource.swift */, + 4A330930282EC7CE00876A3E /* FileImportingServiceSource.swift */, 4AEFF7F127145ADD00D6CB99 /* LogLevelUpdatingServiceSource.swift */, 4AA2531A28216E45003B45EE /* ServiceSource.swift */, 4AA2531828216BFD003B45EE /* UploadRetryingServiceSource.swift */, @@ -1601,6 +1650,20 @@ path = Mocks; sourceTree = ""; }; + 4AD3D7D7282EBDE7008188CD /* CryptomatorIntents */ = { + isa = PBXGroup; + children = ( + 4A753FBB2832A371006A9C3F /* CryptomatorIntents.entitlements */, + 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */, + 4AD3D7DA282EBDE7008188CD /* Info.plist */, + 4AA08E7E28379C6100972A15 /* GetFolderIntentHandler.swift */, + 4A85ECC3283D0DED00E23024 /* GetFolderIntentHandlerError.swift */, + 4AD3D7D8282EBDE7008188CD /* IntentHandler.swift */, + 4A33092A282EBF9900876A3E /* SaveFileIntentHandler.swift */, + ); + path = CryptomatorIntents; + sourceTree = ""; + }; 4ADD9DBC2729618800B73FB9 /* Mocks */ = { isa = PBXGroup; children = ( @@ -1973,6 +2036,27 @@ productReference = 4AA621E4249A6A8400A0BCBD /* FileProviderExtensionUI.appex */; productType = "com.apple.product-type.app-extension"; }; + 4AD3D7D3282EBDE7008188CD /* CryptomatorIntents */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AD3D7E0282EBDE7008188CD /* Build configuration list for PBXNativeTarget "CryptomatorIntents" */; + buildPhases = ( + 4AD3D7D0282EBDE7008188CD /* Sources */, + 4AD3D7D1282EBDE7008188CD /* Frameworks */, + 4AD3D7D2282EBDE7008188CD /* Resources */, + 4A33092F282EC72C00876A3E /* Set Build Number */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CryptomatorIntents; + packageProductDependencies = ( + 4A33092C282EC23400876A3E /* CryptomatorCommonCore */, + ); + productName = CryptomatorIntents; + productReference = 4AD3D7D4282EBDE7008188CD /* CryptomatorIntents.appex */; + productType = "com.apple.product-type.app-extension"; + }; 4AE97DA724572E4900452814 /* Cryptomator */ = { isa = PBXNativeTarget; buildConfigurationList = 4AE97DCF24572E4A00452814 /* Build configuration list for PBXNativeTarget "Cryptomator" */; @@ -1991,6 +2075,7 @@ 4AEE469125263B2E0045DA9F /* PBXTargetDependency */, 4AEE469425263B2E0045DA9F /* PBXTargetDependency */, 4A1673EF270DE4600075C724 /* PBXTargetDependency */, + 4AD3D7DC282EBDE7008188CD /* PBXTargetDependency */, ); name = Cryptomator; packageProductDependencies = ( @@ -2046,7 +2131,7 @@ 4A5E5B212453119100BD6298 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1310; + LastSwiftUpdateCheck = 1330; LastUpgradeCheck = 1250; ORGANIZATIONNAME = "Skymatic GmbH"; TargetAttributes = { @@ -2069,6 +2154,9 @@ 4AA621E3249A6A8400A0BCBD = { CreatedOnToolsVersion = 11.5; }; + 4AD3D7D3282EBDE7008188CD = { + CreatedOnToolsVersion = 13.3.1; + }; 4AE97DA724572E4900452814 = { CreatedOnToolsVersion = 11.4.1; }; @@ -2132,6 +2220,7 @@ 4AA621E3249A6A8400A0BCBD /* FileProviderExtensionUI */, 740375D62587AE7A0023FF53 /* CryptomatorFileProvider */, 4A2245CF24A5E16300DBA437 /* CryptomatorFileProviderTests */, + 4AD3D7D3282EBDE7008188CD /* CryptomatorIntents */, 4A136120276767D60077EB7F /* Snapshots */, ); }; @@ -2177,6 +2266,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4AD3D7D2282EBDE7008188CD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A1A7AC628327419008EEC84 /* Assets.xcassets in Resources */, + 4A1A7AC528326554008EEC84 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4AE97DA624572E4900452814 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2202,6 +2300,25 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 4A33092F282EC72C00876A3E /* Set Build Number */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)", + ); + name = "Set Build Number"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "git=`sh /etc/profile; which git`\nbranchName=`\"$git\" rev-parse --abbrev-ref HEAD`\nappBuild=`\"$git\" rev-list --count $branchName`\nif [ $CONFIGURATION = \"Debug\" ]; then\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $appBuild-$branchName\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nelse\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $appBuild\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nfi\necho \"Updated ${TARGET_BUILD_DIR}/${INFOPLIST_PATH} by setting build number\"\n"; + }; 4A41D25026428D2900B5D787 /* Add URL Schemes */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2320,6 +2437,7 @@ 4A797F9624AC9936007DDBE1 /* CustomCloudProviderMock.swift in Sources */, 4A24822B266E362C002D9F59 /* FileProviderAdapterMoveItemTests.swift in Sources */, 4A24822D266F85FD002D9F59 /* FileProviderAdapterDeleteItemTests.swift in Sources */, + 4AB05A4D283BA362001702D5 /* FileProviderAdapterGetItemIdentifierTests.swift in Sources */, 4AB6A899278F084E0016B01E /* MaintenanceManagerMock.swift in Sources */, 4AB6A890278E1E5D0016B01E /* FileProviderAdapterManagerTests.swift in Sources */, 4A09E54C27071F3C0056D32A /* ErrorMapperTests.swift in Sources */, @@ -2349,6 +2467,7 @@ 4AEECD3B279EB24300C6E2B5 /* NSFileProviderEnumerationObserverMock.swift in Sources */, 4A8F149C266A29E400ADBCE4 /* OnlineItemNameCollisionHandlerTests.swift in Sources */, 4A8F14A2266A302A00ADBCE4 /* FileProviderAdapterTestCase.swift in Sources */, + 4A85ECBE283CBF4700E23024 /* FileImportingServiceSourceTests.swift in Sources */, 4AB1D4F027D20420009060AB /* LocalURLProviderTests.swift in Sources */, 4A3E2FEC271DC9670090BD44 /* MaintenanceManagerTests.swift in Sources */, 4AFBFA182829414A00E30818 /* ProgressManagerMock.swift in Sources */, @@ -2405,6 +2524,18 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4AD3D7D0282EBDE7008188CD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A33092B282EBF9900876A3E /* SaveFileIntentHandler.swift in Sources */, + 4ABB4627282EBE4B00968C5E /* Intents.intentdefinition in Sources */, + 4A85ECC4283D0DED00E23024 /* GetFolderIntentHandlerError.swift in Sources */, + 4AD3D7D9282EBDE7008188CD /* IntentHandler.swift in Sources */, + 4AA08E7F28379C6100972A15 /* GetFolderIntentHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4AE97DA424572E4900452814 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2565,6 +2696,7 @@ 4A644B4B267B4C08008CBB9A /* CreateNewVaultChooseFolderViewController.swift in Sources */, 4A447E1B25BF0DE300D9520D /* ChooseFolderViewController.swift in Sources */, 4A88816427440CE300F7AA6E /* BaseUITableViewController.swift in Sources */, + 4A82C88A2832443D00548F87 /* Intents.intentdefinition in Sources */, 4AF91CE225A7234500ACF01E /* DatabaseManager.swift in Sources */, 4A3C5DDA272BF52600EB7C7A /* TextFieldCellViewModel.swift in Sources */, 4AFCE51F25B89CD80069C4FC /* CloudProviderType+Localization.swift in Sources */, @@ -2622,6 +2754,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4A330931282EC7CE00876A3E /* FileImportingServiceSource.swift in Sources */, 4A8F1498266A299E00ADBCE4 /* OnlineItemNameCollisionHandler.swift in Sources */, 4AEBE8C22653FAD40031487F /* WorkflowMiddleware.swift in Sources */, 4A231B82271EF35400987492 /* DownloadTaskDBManager.swift in Sources */, @@ -2730,6 +2863,11 @@ target = 740375D62587AE7A0023FF53 /* CryptomatorFileProvider */; targetProxy = 4A9BED68268F379300721BAA /* PBXContainerItemProxy */; }; + 4AD3D7DC282EBDE7008188CD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4AD3D7D3282EBDE7008188CD /* CryptomatorIntents */; + targetProxy = 4AD3D7DB282EBDE7008188CD /* PBXContainerItemProxy */; + }; 4AE97DBF24572E4A00452814 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4AE97DA724572E4900452814 /* Cryptomator */; @@ -3123,6 +3261,58 @@ }; name = Release; }; + 4AD3D7DE282EBDE7008188CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = CryptomatorIntents/CryptomatorIntents.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = AUTOMATIC; + DEVELOPMENT_TEAM = YZQJQUHA3L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CryptomatorIntents/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CryptomatorIntents; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Skymatic GmbH. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.2.5; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.ios.intents; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match Development org.cryptomator.ios.intents"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Debug; + }; + 4AD3D7DF282EBDE7008188CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = CryptomatorIntents/CryptomatorIntents.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = AUTOMATIC; + DEVELOPMENT_TEAM = YZQJQUHA3L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CryptomatorIntents/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CryptomatorIntents; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Skymatic GmbH. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.2.5; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.ios.intents; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.cryptomator.ios.intents"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Release; + }; 4AE97DD024572E4A00452814 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3278,6 +3468,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 4AD3D7E0282EBDE7008188CD /* Build configuration list for PBXNativeTarget "CryptomatorIntents" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AD3D7DE282EBDE7008188CD /* Debug */, + 4AD3D7DF282EBDE7008188CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4AE97DCF24572E4A00452814 /* Build configuration list for PBXNativeTarget "Cryptomator" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3324,6 +3523,10 @@ package = 4A1521E227C55EA2006C96B2 /* XCRemoteSwiftPackageReference "TPInAppReceipt" */; productName = TPInAppReceipt; }; + 4A33092C282EC23400876A3E /* CryptomatorCommonCore */ = { + isa = XCSwiftPackageProductDependency; + productName = CryptomatorCommonCore; + }; 4A9172712619F16C003C4043 /* CryptomatorCommonCore */ = { isa = XCSwiftPackageProductDependency; productName = CryptomatorCommonCore; diff --git a/Cryptomator.xcodeproj/xcshareddata/xcschemes/CryptomatorIntents.xcscheme b/Cryptomator.xcodeproj/xcshareddata/xcschemes/CryptomatorIntents.xcscheme new file mode 100644 index 000000000..d08f36789 --- /dev/null +++ b/Cryptomator.xcodeproj/xcshareddata/xcschemes/CryptomatorIntents.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cryptomator/Info.plist b/Cryptomator/Info.plist index 759f0490d..366c6caa3 100644 --- a/Cryptomator/Info.plist +++ b/Cryptomator/Info.plist @@ -56,6 +56,13 @@ NSAllowsArbitraryLoads + NSFaceIDUsageDescription + This lets you unlock your vault. + NSUserActivityTypes + + GetFolderIntent + SaveFileIntent + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -75,7 +82,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSFaceIDUsageDescription - This lets you unlock your vault. diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/FileProviderXPC/FileImporting.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/FileProviderXPC/FileImporting.swift new file mode 100644 index 000000000..adbba85c2 --- /dev/null +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/FileProviderXPC/FileImporting.swift @@ -0,0 +1,65 @@ +// +// FileImporting.swift +// CryptomatorCommonCore +// +// Created by Philipp Schmid on 13.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import FileProvider +import Foundation +import Promises + +@objc public protocol FileImporting: NSFileProviderServiceSource { + /** + Imports the file at the given `localURL` to the given `parentItemIdentifier`. + + - Parameter localURL: The URL of the file to be imported + - Parameter parentItemIdentifier: The item identifier of the folder into which the file will be imported. + - Parameter reply: Reply block which gets called with `nil` once the file has been imported locally. If an error occurs when importing the file locally, the reply block gets called with this error. + */ + func importFile(at localURL: URL, toParentItemIdentifier parentItemIdentifier: String, reply: @escaping (NSError?) -> Void) + + /** + Returns the identifier for an item at the given path. + + - Parameter path: The path of the item in the cloud + - Parameter reply: Reply block which is called with the `rawValue` of the `NSFileProviderItemIdentifier` if there is an item for the passed path in the cloud. If an error occurs while checking the path in the cloud, the reply block is called with it. Furthermore, the reply block is called with an error if the item does not exist in the cloud. + */ + func getIdentifierForItem(at path: String, reply: @escaping (NSString?, NSError?) -> Void) +} + +public extension FileImporting { + /** + Imports the file at the given `localURL` to the given `parentItemIdentifier`. + + - Parameter localURL: The URL of the file to be imported + - Parameter parentItemIdentifier: The item identifier of the folder into which the file will be imported. + */ + func importFile(at localURL: URL, toParentItemIdentifier parentItemIdentifier: String) -> Promise { + return wrap { + self.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier, reply: $0) + }.then { error -> Void in + if let error = error { + throw error + } + } + } + + /** + Returns the identifier for an item at the given path. + + - Parameter path: The path of the item in the cloud + */ + func getIdentifierForItem(at path: String) -> Promise { + return wrap { + self.getIdentifierForItem(at: path, reply: $0) + }.then { + return $0! + } + } +} + +public extension NSFileProviderServiceName { + static let fileImporting = NSFileProviderServiceName("org.cryptomator.ios.file-importing") +} diff --git a/CryptomatorFileProvider/FileProviderAdapter.swift b/CryptomatorFileProvider/FileProviderAdapter.swift index b1001326b..c68a29bf0 100644 --- a/CryptomatorFileProvider/FileProviderAdapter.swift +++ b/CryptomatorFileProvider/FileProviderAdapter.swift @@ -28,6 +28,31 @@ public protocol FileProviderAdapterType: AnyObject { func setFavoriteRank(_ favoriteRank: NSNumber?, forItemIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) func setTagData(_ tagData: Data?, forItemIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) func retryUpload(for itemIdentifier: NSFileProviderItemIdentifier) + func getItemIdentifier(for cloudPath: CloudPath) -> Promise +} + +extension FileProviderAdapterType { + func importDocument(at fileURL: URL, toParentItemIdentifier parentItemIdentifier: NSFileProviderItemIdentifier) async throws -> NSFileProviderItem { + return try await withCheckedThrowingContinuation({ continuation in + importDocument(at: fileURL, toParentItemIdentifier: parentItemIdentifier) { item, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: item!) + } + } + }) + } + + func getItemIdentifier(for cloudPath: CloudPath) async throws -> NSFileProviderItemIdentifier { + return try await withCheckedThrowingContinuation({ continuation in + getItemIdentifier(for: cloudPath).then { + continuation.resume(returning: $0) + }.catch { + continuation.resume(throwing: $0) + } + }) + } } public class FileProviderAdapter: FileProviderAdapterType { @@ -46,8 +71,9 @@ public class FileProviderAdapter: FileProviderAdapterType { private let fullVersionChecker: FullVersionChecker private let workflowFactory: WorkflowFactoryLocking private let domainIdentifier: NSFileProviderDomainIdentifier + private let fileCoordinator: NSFileCoordinator - init(domainIdentifier: NSFileProviderDomainIdentifier, uploadTaskManager: UploadTaskManager, cachedFileManager: CachedFileManager, itemMetadataManager: ItemMetadataManager, reparentTaskManager: ReparentTaskManager, deletionTaskManager: DeletionTaskManager, itemEnumerationTaskManager: ItemEnumerationTaskManager, downloadTaskManager: DownloadTaskManager, scheduler: WorkflowScheduler, provider: CloudProvider, notificator: FileProviderItemUpdateDelegate? = nil, localURLProvider: LocalURLProviderType, fullVersionChecker: FullVersionChecker = UserDefaultsFullVersionChecker.shared) { + init(domainIdentifier: NSFileProviderDomainIdentifier, uploadTaskManager: UploadTaskManager, cachedFileManager: CachedFileManager, itemMetadataManager: ItemMetadataManager, reparentTaskManager: ReparentTaskManager, deletionTaskManager: DeletionTaskManager, itemEnumerationTaskManager: ItemEnumerationTaskManager, downloadTaskManager: DownloadTaskManager, scheduler: WorkflowScheduler, provider: CloudProvider, coordinator: NSFileCoordinator, notificator: FileProviderItemUpdateDelegate? = nil, localURLProvider: LocalURLProviderType, fullVersionChecker: FullVersionChecker = UserDefaultsFullVersionChecker.shared) { self.lastUnlockedDate = Date() self.domainIdentifier = domainIdentifier self.uploadTaskManager = uploadTaskManager @@ -72,6 +98,7 @@ public class FileProviderAdapter: FileProviderAdapterType { self.notificator = notificator self.localURLProvider = localURLProvider self.fullVersionChecker = fullVersionChecker + self.fileCoordinator = coordinator } /** @@ -204,6 +231,12 @@ public class FileProviderAdapter: FileProviderAdapterType { - Postcondition: A `LocalCachedFileInfo` with the url of the copied file exists in the database and has as `correspondingItem` the `id` of the newly created `ItemMetadata` entry. */ func localItemImport(fileURL: URL, parentIdentifier: NSFileProviderItemIdentifier) throws -> LocalItemImportResult { + let stopAccess = fileURL.startAccessingSecurityScopedResource() + defer { + if stopAccess { + fileURL.stopAccessingSecurityScopedResource() + } + } let placeholderMetadata = try createPlaceholderItemForFile(for: fileURL, in: parentIdentifier) let itemIdentifier = convertIDToItemIdentifier(placeholderMetadata.id!) @@ -228,8 +261,6 @@ public class FileProviderAdapter: FileProviderAdapterType { } func copyItem(from sourceURL: URL, to targetURL: URL, itemMetadata: ItemMetadata) throws { - let fileCoordinator = NSFileCoordinator() - let stopAccess = sourceURL.startAccessingSecurityScopedResource() var fileManagerError: NSError? var fileCoordinatorError: NSError? fileCoordinator.coordinate(readingItemAt: sourceURL, options: .withoutChanges, error: &fileCoordinatorError) { _ in @@ -240,9 +271,6 @@ public class FileProviderAdapter: FileProviderAdapterType { fileManagerError = error as NSError } } - if stopAccess { - sourceURL.stopAccessingSecurityScopedResource() - } if let error = fileManagerError ?? fileCoordinatorError { throw error } @@ -316,8 +344,7 @@ public class FileProviderAdapter: FileProviderAdapterType { itemMetadata.statusCode = ItemStatus.isUploading let uploadTaskRecord: UploadTaskRecord do { - let attributes = try FileManager.default.attributesOfItem(atPath: localURL.path) - let lastModifiedDate = attributes[FileAttributeKey.modificationDate] as? Date + let lastModifiedDate = try lastModifiedDateOfItem(at: localURL) try itemMetadataManager.updateMetadata(itemMetadata) try uploadTaskManager.removeTaskRecord(for: itemMetadata) uploadTaskRecord = try uploadTaskManager.createNewTaskRecord(for: itemMetadata) @@ -329,6 +356,24 @@ public class FileProviderAdapter: FileProviderAdapterType { return uploadTaskRecord } + private func lastModifiedDateOfItem(at url: URL) throws -> Date? { + var fileManagerError: NSError? + var fileCoordinatorError: NSError? + var lastModifiedDate: Date? + fileCoordinator.coordinate(readingItemAt: url, options: .withoutChanges, error: &fileCoordinatorError) { _ in + do { + let attributes = try FileManager.default.attributesOfItem(atPath: url.path) + lastModifiedDate = attributes[FileAttributeKey.modificationDate] as? Date + } catch let error as NSError { + fileManagerError = error as NSError + } + } + if let error = fileManagerError ?? fileCoordinatorError { + throw error + } + return lastModifiedDate + } + // MARK: Create Directory public func createDirectory(withName directoryName: String, inParentItemIdentifier parentItemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) { @@ -778,6 +823,45 @@ public class FileProviderAdapter: FileProviderAdapterType { completionHandler(fileProviderItem, nil) } + public func getItemIdentifier(for cloudPath: CloudPath) -> Promise { + if cloudPath == CloudPath("/") { + return Promise(.rootContainer) + } + let parentCloudPath = cloudPath.deletingLastPathComponent() + let parentItemMetadata: ItemMetadata? + do { + parentItemMetadata = try itemMetadataManager.getCachedMetadata(for: parentCloudPath) + } catch { + return Promise(error) + } + let parentItemIdentifier: Promise + + if let parentItemMetadata = parentItemMetadata { + parentItemIdentifier = Promise(NSFileProviderItemIdentifier(domainIdentifier: domainIdentifier, itemID: parentItemMetadata.id!)) + } else { + parentItemIdentifier = getItemIdentifier(for: parentCloudPath) + } + + return parentItemIdentifier.then { + self.enumerateItemsExtensively(for: $0) + }.then { itemList -> NSFileProviderItemIdentifier in + let items = itemList.items + guard let item = items.first(where: { $0.metadata.cloudPath == cloudPath }) else { + throw NSFileProviderError(.noSuchItem) + } + return item.itemIdentifier + } + } + + func enumerateItemsExtensively(for identifier: NSFileProviderItemIdentifier, withPageToken pageToken: String? = nil) -> Promise { + return enumerateItems(for: identifier, withPageToken: pageToken).then { itemList -> Promisein + if let pageToken = pageToken { + return self.enumerateItemsExtensively(for: identifier, withPageToken: pageToken) + } + return Promise(itemList) + } + } + // MARK: Internal /** diff --git a/CryptomatorFileProvider/FileProviderAdapterManager.swift b/CryptomatorFileProvider/FileProviderAdapterManager.swift index a69784ed8..e41c0dc5a 100644 --- a/CryptomatorFileProvider/FileProviderAdapterManager.swift +++ b/CryptomatorFileProvider/FileProviderAdapterManager.swift @@ -31,12 +31,13 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { private let adapterCache: FileProviderAdapterCacheType private let notificatorManager: FileProviderNotificatorManagerType private let queue = DispatchQueue(label: "FileProviderAdapterManager", qos: .userInitiated) + private let providerIdentifier: String convenience init() { - self.init(masterkeyCacheManager: MasterkeyCacheKeychainManager.shared, vaultKeepUnlockedHelper: VaultKeepUnlockedManager.shared, vaultKeepUnlockedSettings: VaultKeepUnlockedManager.shared, vaultManager: VaultDBManager.shared, adapterCache: FileProviderAdapterCache(), notificatorManager: FileProviderNotificatorManager.shared, unlockMonitor: UnlockMonitor()) + self.init(masterkeyCacheManager: MasterkeyCacheKeychainManager.shared, vaultKeepUnlockedHelper: VaultKeepUnlockedManager.shared, vaultKeepUnlockedSettings: VaultKeepUnlockedManager.shared, vaultManager: VaultDBManager.shared, adapterCache: FileProviderAdapterCache(), notificatorManager: FileProviderNotificatorManager.shared, unlockMonitor: UnlockMonitor(), providerIdentifier: NSFileProviderManager.default.providerIdentifier) } - init(masterkeyCacheManager: MasterkeyCacheManager, vaultKeepUnlockedHelper: VaultKeepUnlockedHelper, vaultKeepUnlockedSettings: VaultKeepUnlockedSettings, vaultManager: VaultManager, adapterCache: FileProviderAdapterCacheType, notificatorManager: FileProviderNotificatorManagerType, unlockMonitor: UnlockMonitorType) { + init(masterkeyCacheManager: MasterkeyCacheManager, vaultKeepUnlockedHelper: VaultKeepUnlockedHelper, vaultKeepUnlockedSettings: VaultKeepUnlockedSettings, vaultManager: VaultManager, adapterCache: FileProviderAdapterCacheType, notificatorManager: FileProviderNotificatorManagerType, unlockMonitor: UnlockMonitorType, providerIdentifier: String) { self.masterkeyCacheManager = masterkeyCacheManager self.vaultKeepUnlockedHelper = vaultKeepUnlockedHelper self.vaultKeepUnlockedSettings = vaultKeepUnlockedSettings @@ -44,6 +45,7 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { self.adapterCache = adapterCache self.notificatorManager = notificatorManager self.unlockMonitor = unlockMonitor + self.providerIdentifier = providerIdentifier } public func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType) throws -> FileProviderAdapterType { @@ -143,6 +145,8 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { let itemEnumerationTaskManager = try ItemEnumerationTaskDBManager(database: database) let downloadTaskManager = try DownloadTaskDBManager(database: database) let maintenanceManager = MaintenanceDBManager(database: database) + let fileCoordinator = NSFileCoordinator() + fileCoordinator.purposeIdentifier = providerIdentifier let adapter = FileProviderAdapter(domainIdentifier: domainIdentifier, uploadTaskManager: uploadTaskManager, cachedFileManager: cachedFileManager, @@ -153,6 +157,7 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { downloadTaskManager: downloadTaskManager, scheduler: WorkflowScheduler(maxParallelUploads: 1, maxParallelDownloads: 2), provider: cloudProvider, + coordinator: fileCoordinator, notificator: notificator, localURLProvider: delegate) let workingSetObserver = WorkingSetObserver(domainIdentifier: domainIdentifier, database: database, notificator: notificator, uploadTaskManager: uploadTaskManager, cachedFileManager: cachedFileManager) diff --git a/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift b/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift new file mode 100644 index 000000000..ee708d6c4 --- /dev/null +++ b/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift @@ -0,0 +1,108 @@ +// +// FileImportingServiceSource.swift +// CryptomatorFileProvider +// +// Created by Philipp Schmid on 13.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import FileProvider +import Foundation + +public class FileImportingServiceSource: ServiceSource, FileImporting { + private let domain: NSFileProviderDomain + private let notificator: FileProviderNotificatorType + private let dbPath: URL + private let localURLProvider: LocalURLProviderType + private let adapterManager: FileProviderAdapterProviding + private let fullVersionChecker: FullVersionChecker + + public convenience init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType) { + self.init(domain: domain, + notificator: notificator, + dbPath: dbPath, + delegate: delegate, + adapterManager: FileProviderAdapterManager.shared, + fullVersionChecker: UserDefaultsFullVersionChecker.shared) + } + + init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, adapterManager: FileProviderAdapterProviding, fullVersionChecker: FullVersionChecker) { + self.domain = domain + self.notificator = notificator + self.dbPath = dbPath + self.localURLProvider = delegate + self.adapterManager = adapterManager + self.fullVersionChecker = fullVersionChecker + super.init(serviceName: .fileImporting, exportedInterface: NSXPCInterface(with: FileImporting.self)) + } + + public func getIdentifierForItem(at path: String, reply: @escaping (NSString?, NSError?) -> Void) { + Task { + do { + let identifier = try await getIdentifierForItem(at: path) + reply(identifier, nil) + } catch { + reply(nil, error as NSError) + } + } + } + + func getIdentifierForItem(at path: String) async throws -> NSString { + let adapter: FileProviderAdapterType + do { + adapter = try adapterManager.getAdapter(forDomain: domain, + dbPath: dbPath, + delegate: localURLProvider, + notificator: notificator) + } catch { + throw ErrorWrapper.wrapError(error, domain: domain)._nsError + } + let cloudPath = CloudPath(path) + let itemIdentifier = try await adapter.getItemIdentifier(for: cloudPath) + return itemIdentifier.rawValue as NSString + } + + public func importFile(at localURL: URL, toParentItemIdentifier parentItemIdentifier: String, reply: @escaping (NSError?) -> Void) { + Task { + do { + try await importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier) + reply(nil) + } catch let error as NSError { + var userInfo = error.userInfo + userInfo[NSFileProviderErrorItemKey] = nil + let modifiedError = NSError(domain: error.domain, code: error.code, userInfo: userInfo) + reply(modifiedError) + } + } + } + + func importFile(at localURL: URL, toParentItemIdentifier parentItemIdentifier: String) async throws { + guard fullVersionChecker.isFullVersion else { + throw XPCErrorHelper.bridgeError(FileImportingServiceSourceError.missingPremium) + } + let adapter: FileProviderAdapterType + do { + adapter = try adapterManager.getAdapter(forDomain: domain, + dbPath: dbPath, + delegate: localURLProvider, + notificator: notificator) + } catch { + throw ErrorWrapper.wrapError(error, domain: domain)._nsError + } + let parentItemIdentifier = NSFileProviderItemIdentifier(parentItemIdentifier) + _ = try await adapter.importDocument(at: localURL, toParentItemIdentifier: parentItemIdentifier) + } +} + +enum FileImportingServiceSourceError: Error, LocalizedError { + case missingPremium + + var errorDescription: String? { + switch self { + case .missingPremium: + return LocalizedString.getValue("fileProvider.fileImporting.error.missingPremium") + } + } +} diff --git a/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift b/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift new file mode 100644 index 000000000..fb035e914 --- /dev/null +++ b/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift @@ -0,0 +1,129 @@ +// +// FileImportingServiceSourceTests.swift +// CryptomatorFileProviderTests +// +// Created by Philipp Schmid on 24.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import XCTest +@testable import CryptomatorCommonCore +@testable import CryptomatorFileProvider +@testable import Promises + +class FileImportingServiceSourceTests: XCTestCase { + var serviceSource: FileImportingServiceSource! + var notificatorMock: FileProviderNotificatorTypeMock! + var adapterProvidingMock: FileProviderAdapterProvidingMock! + var urlProviderMock: LocalURLProviderMock! + var fullVersionCheckerMock: FullVersionCheckerMock! + let dbPath = FileManager.default.temporaryDirectory + let domain = NSFileProviderDomain(identifier: .test, displayName: "Foo", pathRelativeToDocumentStorage: "/") + let itemStub = FileProviderItem(metadata: .init(name: "Foo", type: .file, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: CloudPath("/foo"), isPlaceholderItem: false), domainIdentifier: .test) + + override func setUpWithError() throws { + notificatorMock = FileProviderNotificatorTypeMock() + urlProviderMock = LocalURLProviderMock() + adapterProvidingMock = FileProviderAdapterProvidingMock() + fullVersionCheckerMock = FullVersionCheckerMock() + fullVersionCheckerMock.isFullVersion = true + serviceSource = FileImportingServiceSource(domain: domain, + notificator: notificatorMock, + dbPath: dbPath, + delegate: urlProviderMock, + adapterManager: adapterProvidingMock, + fullVersionChecker: fullVersionCheckerMock) + } + + func testGetItemIdentifier() throws { + let cloudPath = "/foo/bar" + let adapterMock = FileProviderAdapterTypeMock() + let expectedItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) + adapterMock.getItemIdentifierForReturnValue = Promise(expectedItemIdentifier) + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + let itemIdentifierPromise = serviceSource.getIdentifierForItem(at: cloudPath) + wait(for: itemIdentifierPromise, timeout: 1.0) + let rawItemIdentifier = try XCTUnwrap(itemIdentifierPromise.value) + let itemIdentifier = NSFileProviderItemIdentifier(rawValue: rawItemIdentifier as String) + + XCTAssertEqual(expectedItemIdentifier, itemIdentifier) + assertAdapterProvidingMockGetAdapterCalled() + } + + func testGetItemIdentifierForLockedVault() throws { + let cloudPath = "/foo/bar" + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + let itemIdentifierPromise = serviceSource.getIdentifierForItem(at: cloudPath) + let expectedWrappedError = ErrorWrapper.wrapError(UnlockMonitorError.defaultLock, domain: domain) + XCTAssertRejects(itemIdentifierPromise, with: expectedWrappedError._nsError) + } + + func testGetItemIdentifierForTrial() throws { + fullVersionCheckerMock.isFullVersion = false + try testGetItemIdentifier() + } + + func testImportFile() throws { + let adapterMock = FileProviderAdapterTypeMock() + let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) + + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerClosure = { _, _, completion in + completion(self.itemStub, nil) + } + let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let importFilePromise = serviceSource.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier.rawValue) + wait(for: importFilePromise, timeout: 1.0) + let adapterMockReceivedArguments = adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerReceivedArguments + XCTAssertEqual(1, adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerCallsCount) + XCTAssertEqual(localURL, adapterMockReceivedArguments?.fileURL) + XCTAssertEqual(parentItemIdentifier, adapterMockReceivedArguments?.parentItemIdentifier) + assertAdapterProvidingMockGetAdapterCalled() + } + + func testImportFileFailWithFilenameCollisionErrorWithoutAssociatedItem() throws { + let adapterMock = FileProviderAdapterTypeMock() + let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) + + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerClosure = { _, _, completion in + completion(nil, NSError.fileProviderErrorForCollision(with: self.itemStub)) + } + let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let importFilePromise = serviceSource.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier.rawValue) + // Rejects with plain filenameCollision error without the itemStub - necessary for XPC as FileProviderItem does not support secure coding + XCTAssertRejects(importFilePromise, with: NSFileProviderError(.filenameCollision)) + let adapterMockReceivedArguments = adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerReceivedArguments + XCTAssertEqual(1, adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerCallsCount) + XCTAssertEqual(localURL, adapterMockReceivedArguments?.fileURL) + XCTAssertEqual(parentItemIdentifier, adapterMockReceivedArguments?.parentItemIdentifier) + assertAdapterProvidingMockGetAdapterCalled() + } + + func testImportFileForLockedVault() throws { + let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + let importFilePromise = serviceSource.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier.rawValue) + let expectedWrappedError = ErrorWrapper.wrapError(UnlockMonitorError.defaultLock, domain: domain) + XCTAssertRejects(importFilePromise, with: expectedWrappedError._nsError) + } + + func testImportFileForTrial() throws { + fullVersionCheckerMock.isFullVersion = false + let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) + let importFilePromise = serviceSource.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier.rawValue) + let expectedWrappedError = XPCErrorHelper.bridgeError(FileImportingServiceSourceError.missingPremium) + XCTAssertRejects(importFilePromise, with: expectedWrappedError) + } + + private func assertAdapterProvidingMockGetAdapterCalled() { + let adapterProviderManagerReceivedArguments = adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReceivedArguments + XCTAssertEqual(domain, adapterProviderManagerReceivedArguments?.domain) + XCTAssertEqual(dbPath, adapterProviderManagerReceivedArguments?.dbPath) + XCTAssert(urlProviderMock === adapterProviderManagerReceivedArguments?.delegate) + XCTAssert(notificatorMock === adapterProviderManagerReceivedArguments?.notificator) + } +} diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift index f1f910236..3e5bc78ed 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift @@ -15,7 +15,7 @@ class FileProviderAdapterCreateDirectoryTests: FileProviderAdapterTestCase { let expectation = XCTestExpectation() let rootItemMetadata = ItemMetadata(id: metadataManagerMock.getRootContainerID(), name: "Home", type: .folder, size: nil, parentID: metadataManagerMock.getRootContainerID(), lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/"), isPlaceholderItem: false) try metadataManagerMock.cacheMetadata(rootItemMetadata) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) adapter.createDirectory(withName: "TestFolder", inParentItemIdentifier: .rootContainer) { item, error in XCTAssertNil(error) guard let fileProviderItem = item as? FileProviderItem else { @@ -40,7 +40,7 @@ class FileProviderAdapterCreateDirectoryTests: FileProviderAdapterTestCase { func testCreateDirectoryFailsIfParentDoesNotExist() throws { let expectation = XCTestExpectation() - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, localURLProvider: LocalURLProviderMock()) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: LocalURLProviderMock()) adapter.createDirectory(withName: "TestFolder", inParentItemIdentifier: NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2)) { item, error in XCTAssertNil(item) guard let error = error else { diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterGetItemIdentifierTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterGetItemIdentifierTests.swift new file mode 100644 index 000000000..676391e24 --- /dev/null +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterGetItemIdentifierTests.swift @@ -0,0 +1,112 @@ +// +// FileProviderAdapterGetItemIdentifierTests.swift +// CryptomatorFileProviderTests +// +// Created by Philipp Schmid on 23.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import XCTest +@testable import CryptomatorFileProvider +@testable import Promises + +class FileProviderAdapterGetItemIdentifierTests: FileProviderAdapterTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + uploadTaskManagerMock.getCorrespondingTaskRecordsIdsClosure = { + $0.map { _ in return nil } + } + metadataManagerMock.cachedMetadata[NSFileProviderItemIdentifier.rootContainerDatabaseValue] = ItemMetadata(id: NSFileProviderItemIdentifier.rootContainerDatabaseValue, + name: "Home", + type: .folder, + size: nil, + parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, + lastModifiedDate: nil, + statusCode: .isUploaded, + cloudPath: CloudPath("/"), + isPlaceholderItem: false) + } + + func testGetItemIdentifierForRoot() throws { + let cloudPath = CloudPath("/") + let getItemIdentifierPromise = adapter.getItemIdentifier(for: cloudPath) + wait(for: getItemIdentifierPromise, timeout: 1.0) + let itemIdentifier = try XCTUnwrap(getItemIdentifierPromise.value) + XCTAssertEqual(.rootContainer, itemIdentifier) + } + + func testGetItemIdentifierForSubFolder() throws { + let cloudPath = CloudPath("/Directory 1/Directory 2") + let folderMetadata = ItemMetadata(id: 2, + name: "Directory 1", + type: .folder, + size: nil, + parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, + lastModifiedDate: nil, + statusCode: .isUploaded, + cloudPath: CloudPath("/Directory 1"), + isPlaceholderItem: false) + + let folderMetadataID = try XCTUnwrap(folderMetadata.id) + metadataManagerMock.cachedMetadata[folderMetadataID] = folderMetadata + + let subFolderMetadata = ItemMetadata(id: 3, + name: "Directory 2", + type: .folder, + size: nil, + parentID: folderMetadata.id!, + lastModifiedDate: nil, + statusCode: .isUploaded, + cloudPath: CloudPath("/Directory 1/Directory 2"), + isPlaceholderItem: false) + let subFolderMetadataID = try XCTUnwrap(subFolderMetadata.id) + metadataManagerMock.cachedMetadata[subFolderMetadataID] = subFolderMetadata + + let getItemIdentifierPromise = adapter.getItemIdentifier(for: cloudPath) + wait(for: getItemIdentifierPromise, timeout: 1.0) + let itemIdentifier = try XCTUnwrap(getItemIdentifierPromise.value) + let expectedItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: subFolderMetadataID) + XCTAssertEqual(expectedItemIdentifier, itemIdentifier) + } + + func testGetItemIdentifierForCachedItemMissingInCloud() throws { + let cloudPath = CloudPath("/Directory 1/Directory 3") + let folderMetadata = ItemMetadata(id: 2, + name: "Directory 1", + type: .folder, + size: nil, + parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, + lastModifiedDate: nil, + statusCode: .isUploaded, + cloudPath: CloudPath("/Directory 1"), + isPlaceholderItem: false) + + let folderMetadataID = try XCTUnwrap(folderMetadata.id) + metadataManagerMock.cachedMetadata[folderMetadataID] = folderMetadata + + let subFolderMetadata = ItemMetadata(id: 3, + name: "Directory 3", + type: .folder, + size: nil, + parentID: folderMetadata.id!, + lastModifiedDate: nil, + statusCode: .isUploaded, + cloudPath: CloudPath("/Directory 1/Directory 3"), + isPlaceholderItem: false) + let subFolderMetadataID = try XCTUnwrap(subFolderMetadata.id) + metadataManagerMock.cachedMetadata[subFolderMetadataID] = subFolderMetadata + + let getItemIdentifierPromise = adapter.getItemIdentifier(for: cloudPath) + XCTAssertRejects(getItemIdentifierPromise, with: NSFileProviderError(.noSuchItem)._nsError) + } + + func testGetItemIdentifierForItemNotYetCached() throws { + let cloudPath = CloudPath("/Directory 1/Directory 2") + let getItemIdentifierPromise = adapter.getItemIdentifier(for: cloudPath) + wait(for: getItemIdentifierPromise, timeout: 1.0) + let itemIdentifier = try XCTUnwrap(getItemIdentifierPromise.value) + let expectedItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 7) + XCTAssertEqual(expectedItemIdentifier, itemIdentifier) + } +} diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift index b9805f8e8..7bf7aa7d9 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift @@ -27,7 +27,7 @@ class FileProviderAdapterImportDirectoryTests: FileProviderAdapterTestCase { metadataManagerMock.cachedMetadata[1] = ItemMetadata(item: .init(name: "/", cloudPath: CloudPath("/"), itemType: .folder, lastModifiedDate: nil, size: nil), withParentID: 1) let provider = CloudProviderGraphMock() let scheduler = WorkflowSchedulerMock(maxParallelUploads: 2, maxParallelDownloads: 2) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: scheduler, provider: provider, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: scheduler, provider: provider, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) var parentIdentifier: NSFileProviderItemIdentifier = .rootContainer for _ in 0 ..< 5 { diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift index 12e5def44..b0372933e 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift @@ -219,7 +219,7 @@ class FileProviderAdapterImportDocumentTests: FileProviderAdapterTestCase { let fileContent = "TestContent" try fileContent.write(to: fileURL, atomically: true, encoding: .utf8) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) adapter.deleteItem(withIdentifier: NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: itemID), completionHandler: ({ error in XCTAssertNil(error) diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift index f9e048b63..294f8dbe8 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift @@ -13,6 +13,7 @@ import XCTest @testable import CryptomatorFileProvider class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { + let fileCoordinator = NSFileCoordinator() var adapter: FileProviderAdapter! var localURLProviderMock: LocalURLProviderMock! var fullVersionCheckerMock: FullVersionCheckerMock! @@ -35,6 +36,7 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowScheduler(maxParallelUploads: 1, maxParallelDownloads: 1), provider: cloudProviderMock, + coordinator: fileCoordinator, notificator: fileProviderItemUpdateDelegateMock, localURLProvider: localURLProviderMock, fullVersionChecker: fullVersionCheckerMock) @@ -59,7 +61,7 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { } func createFullyMockedAdapter() -> FileProviderAdapter { - return FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, localURLProvider: localURLProviderMock) + return FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) } } diff --git a/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift b/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift index f6c40107f..22f4bca3f 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift @@ -14,6 +14,7 @@ import XCTest @testable import CryptomatorFileProvider class FileProviderAdapterManagerTests: XCTestCase { + let providerIdentifier = UUID().uuidString let vaultUID = "VaultUID-12345" lazy var domain = NSFileProviderDomain(vaultUID: vaultUID, displayName: "TestVault") var fileProviderAdapterManager: FileProviderAdapterManager! @@ -43,7 +44,7 @@ class FileProviderAdapterManagerTests: XCTestCase { workingSetObservationMock = WorkingSetObservingMock() fileProviderNotificatorMock = FileProviderNotificatorTypeMock() localURLProviderMock = LocalURLProviderMock() - fileProviderAdapterManager = FileProviderAdapterManager(masterkeyCacheManager: masterkeyCacheManagerMock, vaultKeepUnlockedHelper: vaultKeepUnlockedHelperMock, vaultKeepUnlockedSettings: vaultKeepUnlockedSettingsMock, vaultManager: vaultManagerMock, adapterCache: adapterCacheMock, notificatorManager: notificatorManagerMock, unlockMonitor: UnlockMonitor()) + fileProviderAdapterManager = FileProviderAdapterManager(masterkeyCacheManager: masterkeyCacheManagerMock, vaultKeepUnlockedHelper: vaultKeepUnlockedHelperMock, vaultKeepUnlockedSettings: vaultKeepUnlockedSettingsMock, vaultManager: vaultManagerMock, adapterCache: adapterCacheMock, notificatorManager: notificatorManagerMock, unlockMonitor: UnlockMonitor(), providerIdentifier: providerIdentifier) tmpURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) dbPath = tmpURL.appendingPathComponent("db.sqlite", isDirectory: false) try FileManager.default.createDirectory(at: tmpURL, withIntermediateDirectories: false) diff --git a/CryptomatorFileProviderTests/Mocks/FileProviderAdapterTypeMock.swift b/CryptomatorFileProviderTests/Mocks/FileProviderAdapterTypeMock.swift index c896091a5..a5396f264 100644 --- a/CryptomatorFileProviderTests/Mocks/FileProviderAdapterTypeMock.swift +++ b/CryptomatorFileProviderTests/Mocks/FileProviderAdapterTypeMock.swift @@ -6,6 +6,7 @@ // Copyright © 2022 Skymatic GmbH. All rights reserved. // +import CryptomatorCloudAccessCore import CryptomatorFileProvider import FileProvider import Foundation @@ -281,6 +282,25 @@ final class FileProviderAdapterTypeMock: FileProviderAdapterType { retryUploadForReceivedInvocations.append(itemIdentifier) retryUploadForClosure?(itemIdentifier) } + + // MARK: - getItemIdentifier + + var getItemIdentifierForCallsCount = 0 + var getItemIdentifierForCalled: Bool { + getItemIdentifierForCallsCount > 0 + } + + var getItemIdentifierForReceivedArguments: CloudPath? + var getItemIdentifierForReceivedInvocations: [CloudPath] = [] + var getItemIdentifierForReturnValue: Promise! + var getItemIdentifierForClosure: ((CloudPath) -> Promise)? + + func getItemIdentifier(for cloudPath: CloudPath) -> Promise { + getItemIdentifierForCallsCount += 1 + getItemIdentifierForReceivedArguments = cloudPath + getItemIdentifierForReceivedInvocations.append(cloudPath) + return getItemIdentifierForClosure.map({ $0(cloudPath) }) ?? getItemIdentifierForReturnValue + } } // swiftlint:enable all diff --git a/CryptomatorIntents/CryptomatorIntents.entitlements b/CryptomatorIntents/CryptomatorIntents.entitlements new file mode 100644 index 000000000..fc240c9c9 --- /dev/null +++ b/CryptomatorIntents/CryptomatorIntents.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.org.cryptomator.ios + + + diff --git a/CryptomatorIntents/GetFolderIntentHandler.swift b/CryptomatorIntents/GetFolderIntentHandler.swift new file mode 100644 index 000000000..63f45dc08 --- /dev/null +++ b/CryptomatorIntents/GetFolderIntentHandler.swift @@ -0,0 +1,142 @@ +// +// GetFolderIntentHandler.swift +// CryptomatorIntents +// +// Created by Philipp Schmid on 20.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CocoaLumberjackSwift +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +import Intents +import Promises + +class GetFolderIntentHandler: NSObject, GetFolderIntentHandling { + private let vaultAccountManager: VaultAccountManager + private let cloudProviderAccountManager: CloudProviderAccountManager + + override convenience init() { + GetFolderIntentHandler.oneTimeSetup() + self.init(vaultAccountManager: VaultAccountDBManager.shared, cloudProviderAccountManager: CloudProviderAccountDBManager.shared) + } + + init(vaultAccountManager: VaultAccountManager, cloudProviderAccountManager: CloudProviderAccountManager) { + self.vaultAccountManager = vaultAccountManager + self.cloudProviderAccountManager = cloudProviderAccountManager + } + + func handle(intent: GetFolderIntent) async -> GetFolderIntentResponse { + guard let path = intent.path else { + return .failure(GetFolderIntentHandlerError.missingPath) + } + guard let vault = intent.vault, let vaultIdentifier = vault.identifier else { + return .failure(GetFolderIntentHandlerError.noVaultSelected) + } + let cloudPath = CloudPath(path) + let domainIdentifier = NSFileProviderDomainIdentifier(rawValue: vaultIdentifier) + let folderIdentifier: String + do { + folderIdentifier = try await getIdentifierForFolder(at: cloudPath, domainIdentifier: domainIdentifier) + } catch NSFileProviderError.notAuthenticated { + return GetFolderIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.lockedVault")) + } catch FileProviderXPCConnectorError.domainNotFound { + return GetFolderIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.selectedVaultNotFound")) + } catch { + return .failure(error) + } + let vaultName = vault.displayString + let displayName = "\(vaultName):\(cloudPath.path)" + let vaultFolder = VaultFolder(identifier: folderIdentifier, display: displayName) + vaultFolder.vaultIdentifier = vaultIdentifier + return .success(vaultFolder: vaultFolder) + } + + func confirm(intent: GetFolderIntent) async -> GetFolderIntentResponse { + guard let path = intent.path, !path.isEmpty else { + return .failure(GetFolderIntentHandlerError.missingPath) + } + if intent.vault == nil || intent.vault?.identifier == nil { + return .failure(GetFolderIntentHandlerError.noVaultSelected) + } + return GetFolderIntentResponse(code: .success, userActivity: nil) + } + + @available(iOSApplicationExtension 14.0, *) + func provideVaultOptionsCollection(for intent: GetFolderIntent) async throws -> INObjectCollection { + let vaultAccounts = try vaultAccountManager.getAllAccounts() + let vaults: [Vault] = try vaultAccounts.map { + let cloudProviderType = try cloudProviderAccountManager.getCloudProviderType(for: $0.delegateAccountUID) + return Vault(identifier: $0.vaultUID, + display: $0.vaultName, + subtitle: $0.vaultPath.path, + image: .init(type: cloudProviderType)) + } + return INObjectCollection(items: vaults) + } + + @available(iOS, introduced: 13.0, deprecated: 14.0, message: "") + func provideVaultOptions(for intent: GetFolderIntent, with completion: @escaping ([Vault]?, Error?) -> Void) { + do { + let vaultAccounts = try vaultAccountManager.getAllAccounts() + let vaults: [Vault] = vaultAccounts.map { + return Vault(identifier: $0.vaultUID, display: $0.vaultName) + } + completion(vaults, nil) + } catch { + completion(nil, error) + } + } + + // MARK: Internal + + private static var oneTimeSetup: () -> Void = { + // Set up logger + LoggerSetup.oneTimeSetup() + if let dbURL = CryptomatorDatabase.sharedDBURL { + do { + let dbPool = try CryptomatorDatabase.openSharedDatabase(at: dbURL) + CryptomatorDatabase.shared = try CryptomatorDatabase(dbPool) + } catch { + DDLogError("Open shared database at \(dbURL) failed with error: \(error)") + } + } + } + + private func getIdentifierForFolder(at cloudPath: CloudPath, domainIdentifier: NSFileProviderDomainIdentifier) async throws -> String { + let getXPCPromise: Promise> = FileProviderXPCConnector.shared.getXPC(serviceName: .fileImporting, domainIdentifier: domainIdentifier) + return try await withCheckedThrowingContinuation({ continuation in + getXPCPromise.then { xpc in + xpc.proxy.getIdentifierForItem(at: cloudPath.path) + }.then { + continuation.resume(returning: $0 as String) + }.catch { + continuation.resume(throwing: $0) + }.always { + FileProviderXPCConnector.shared.invalidateXPC(getXPCPromise) + } + }) + } +} + +extension GetFolderIntentResponse { + static func success(vaultFolder: VaultFolder) -> GetFolderIntentResponse { + let response = GetFolderIntentResponse(code: .success, userActivity: nil) + response.vaultFolder = vaultFolder + return response + } + + static func failure(_ error: Error) -> GetFolderIntentResponse { + return GetFolderIntentResponse(error: error) + } + + convenience init(error: Error) { + self.init(failureReason: error.localizedDescription) + } + + convenience init(failureReason: String) { + self.init(code: .failure, userActivity: nil) + self.failureReason = failureReason + } +} diff --git a/CryptomatorIntents/GetFolderIntentHandlerError.swift b/CryptomatorIntents/GetFolderIntentHandlerError.swift new file mode 100644 index 000000000..91f7c24b3 --- /dev/null +++ b/CryptomatorIntents/GetFolderIntentHandlerError.swift @@ -0,0 +1,24 @@ +// +// GetFolderIntentHandlerError.swift +// CryptomatorIntents +// +// Created by Philipp Schmid on 24.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCommonCore +import Foundation + +enum GetFolderIntentHandlerError: Error, LocalizedError { + case missingPath + case noVaultSelected + + var errorDescription: String? { + switch self { + case .missingPath: + return LocalizedString.getValue("getFolderIntent.error.missingPath") + case .noVaultSelected: + return LocalizedString.getValue("getFolderIntent.error.noVaultSelected") + } + } +} diff --git a/CryptomatorIntents/Info.plist b/CryptomatorIntents/Info.plist new file mode 100644 index 000000000..b71480964 --- /dev/null +++ b/CryptomatorIntents/Info.plist @@ -0,0 +1,25 @@ + + + + + NSExtension + + NSExtensionAttributes + + IntentsRestrictedWhileLocked + + IntentsRestrictedWhileProtectedDataUnavailable + + IntentsSupported + + GetFolderIntent + SaveFileIntent + + + NSExtensionPointIdentifier + com.apple.intents-service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).IntentHandler + + + diff --git a/CryptomatorIntents/IntentHandler.swift b/CryptomatorIntents/IntentHandler.swift new file mode 100644 index 000000000..fdbc953c5 --- /dev/null +++ b/CryptomatorIntents/IntentHandler.swift @@ -0,0 +1,21 @@ +// +// IntentHandler.swift +// CryptomatorIntents +// +// Created by Philipp Schmid on 13.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import Intents + +class IntentHandler: INExtension { + override func handler(for intent: INIntent) -> Any { + if intent is SaveFileIntent { + return SaveFileIntentHandler() + } + if intent is GetFolderIntent { + return GetFolderIntentHandler() + } + return self + } +} diff --git a/CryptomatorIntents/Intents.intentdefinition b/CryptomatorIntents/Intents.intentdefinition new file mode 100644 index 000000000..b50b293b2 --- /dev/null +++ b/CryptomatorIntents/Intents.intentdefinition @@ -0,0 +1,557 @@ + + + + + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + YO2vX8 + INIntentDefinitionSystemVersion + 21F79 + INIntentDefinitionToolsBuildVersion + 13F17a + INIntentDefinitionToolsVersion + 13.4 + INIntents + + + INIntentCategory + generic + INIntentConfigurable + + INIntentDefaultImageName + bot + INIntentDescription + Saves a file to a vault + INIntentDescriptionID + UN1SvF + INIntentIneligibleForSuggestions + + INIntentInput + file + INIntentKeyParameter + file + INIntentLastParameterTag + 13 + INIntentManagedParameterCombinations + + file,folder,ignoreExisting + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Save ${file} to ${folder} + INIntentParameterCombinationTitleID + PkFbjn + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + SaveFile + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + File + INIntentParameterDisplayNameID + AJzrnx + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCustomUTIs + + + UTI + public.data + + + UTI + public.image + + + UTI + public.content + + + INIntentParameterMetadataType + Custom + + INIntentParameterName + file + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 13 + INIntentParameterType + File + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Folder + INIntentParameterDisplayNameID + 2o44Pd + INIntentParameterDisplayPriority + 2 + INIntentParameterName + folder + INIntentParameterObjectType + VaultFolder + INIntentParameterObjectTypeNamespace + YO2vX8 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 11 + INIntentParameterType + Object + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Ignore existing file with same name + INIntentParameterDisplayNameID + ft89SM + INIntentParameterDisplayPriority + 3 + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + + INIntentParameterMetadataFalseDisplayName + false + INIntentParameterMetadataFalseDisplayNameID + WrUDZT + INIntentParameterMetadataTrueDisplayName + true + INIntentParameterMetadataTrueDisplayNameID + 3g4nj9 + + INIntentParameterName + ignoreExisting + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 9 + INIntentParameterType + Boolean + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeConciseFormatString + ${failureReason} + INIntentResponseCodeConciseFormatStringID + NSH4gZ + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 2 + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Failure Reason + INIntentResponseParameterDisplayNameID + CjPrfS + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + failureReason + INIntentResponseParameterTag + 2 + INIntentResponseParameterType + String + + + + INIntentTitle + Save File + INIntentTitleID + dlePJ1 + INIntentType + Custom + INIntentVerb + Do + + + INIntentCategory + generic + INIntentConfigurable + + INIntentDescriptionID + dh0Nnc + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 6 + INIntentManagedParameterCombinations + + path,vault + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Get folder located at ${path} in ${vault} + INIntentParameterCombinationTitleID + getFolderIntent.test + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + GetFolder + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Path + INIntentParameterDisplayNameID + 0wBj7w + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + Evx5fB + + INIntentParameterName + path + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 4 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Vault + INIntentParameterDisplayNameID + W0waaV + INIntentParameterDisplayPriority + 2 + INIntentParameterName + vault + INIntentParameterObjectType + Vault + INIntentParameterObjectTypeNamespace + YO2vX8 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 6 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeConciseFormatString + ${failureReason} + INIntentResponseCodeConciseFormatStringID + KDmUbi + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 7 + INIntentResponseOutput + vaultFolder + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Folder + INIntentResponseParameterDisplayNameID + 7aJMCw + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + vaultFolder + INIntentResponseParameterObjectType + VaultFolder + INIntentResponseParameterObjectTypeNamespace + YO2vX8 + INIntentResponseParameterTag + 6 + INIntentResponseParameterType + Object + + + INIntentResponseParameterDisplayName + Failure Reason + INIntentResponseParameterDisplayNameID + EdwH3n + INIntentResponseParameterDisplayPriority + 2 + INIntentResponseParameterName + failureReason + INIntentResponseParameterTag + 7 + INIntentResponseParameterType + String + + + + INIntentTitle + Get Folder + INIntentTitleID + Fdd6ta + INIntentType + Custom + INIntentVerb + Do + + + INTypes + + + INTypeDisplayName + Vault + INTypeDisplayNameID + lXc2vD + INTypeLastPropertyTag + 99 + INTypeName + Vault + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + + + INTypeDisplayName + VaultFolder + INTypeDisplayNameID + 9EErZb + INTypeLastPropertyTag + 100 + INTypeName + VaultFolder + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + INTypePropertyDisplayName + Vault Identifier + INTypePropertyDisplayNameID + BJqJ7P + INTypePropertyDisplayPriority + 5 + INTypePropertyName + vaultIdentifier + INTypePropertyTag + 100 + INTypePropertyType + String + + + + + + diff --git a/CryptomatorIntents/SaveFileIntentHandler.swift b/CryptomatorIntents/SaveFileIntentHandler.swift new file mode 100644 index 000000000..76f104435 --- /dev/null +++ b/CryptomatorIntents/SaveFileIntentHandler.swift @@ -0,0 +1,167 @@ +// +// SaveFileIntentHandler.swift +// CryptomatorIntents +// +// Created by Philipp Schmid on 13.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import CocoaLumberjackSwift +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import FileProvider +import Foundation +import Intents +import Promises + +class SaveFileIntentHandler: NSObject, SaveFileIntentHandling { + func handle(intent: SaveFileIntent) async -> SaveFileIntentResponse { + guard let vaultFolder = intent.folder, let vaultIdentifier = vaultFolder.vaultIdentifier, let folderIdentifier = vaultFolder.identifier else { + return SaveFileIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.invalidFolder")) + } + guard let fileURL = intent.file?.fileURL else { + return SaveFileIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.missingFile")) + } + guard let tmpFolderURL = FileManager.default.appGroupCacheDirectory?.appendingPathComponent(UUID().uuidString) else { + return SaveFileIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.missingTemporaryFolder")) + } + let tmpFileURL = tmpFolderURL.appendingPathComponent(fileURL.lastPathComponent) + let shouldStopAccess = fileURL.startAccessingSecurityScopedResource() + defer { + if shouldStopAccess { + fileURL.stopAccessingSecurityScopedResource() + } + try? FileManager.default.removeItem(at: tmpFolderURL) + } + do { + try FileManager.default.createDirectory(at: tmpFolderURL, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.copyItem(at: fileURL, to: tmpFileURL) + } catch { + DDLogError("Copy item to shared tmp folder failed with error: \(error)") + return SaveFileIntentResponse(error: error) + } + + let domainIdentifier = NSFileProviderDomainIdentifier(vaultIdentifier) + do { + try await importFile(at: tmpFileURL, toParentItemIdentifier: folderIdentifier, domainIdentifier: domainIdentifier) + } catch NSFileProviderError.notAuthenticated { + return SaveFileIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.lockedVault")) + } catch NSFileProviderError.filenameCollision { + if intent.shouldIgnoreExisting { + // Ignore filename collision - warning this also ignores changes to existing files! + return .success + } else { + return .failure(CocoaError(.fileWriteFileExists)) + } + } catch FileProviderXPCConnectorError.domainNotFound { + return SaveFileIntentResponse(failureReason: LocalizedString.getValue("intents.saveFile.selectedVaultNotFound")) + } catch { + return SaveFileIntentResponse(error: error) + } + return .success + } + + @available(iOSApplicationExtension 14.0, *) + func provideFolderOptionsCollection(for intent: SaveFileIntent) async throws -> INObjectCollection { + // Returns an empty collection, since dynamic options are only supported to use the magic variable in the Shortcuts app + return INObjectCollection(items: []) + } + + @available(iOS, introduced: 13.0, deprecated: 14.0, message: "") + func provideFolderOptions(for intent: SaveFileIntent, with completion: @escaping ([VaultFolder]?, Error?) -> Void) { + // Returns an empty collection, since dynamic options are only supported to use the magic variable in the Shortcuts app + completion([], nil) + } + + @available(iOSApplicationExtension 14.0, *) + func provideFileOptionsCollection(for intent: SaveFileIntent) async throws -> INObjectCollection { + return INObjectCollection(items: []) + } + + @available(iOS, introduced: 13.0, deprecated: 14.0, message: "") + func provideFileOptionsCollection(for intent: SaveFileIntent, with completion: @escaping ([INFile]?, Error?) -> Void) { + // Returns an empty collection, since dynamic options are only supported to use the magic variable in the Shortcuts app + completion([], nil) + } + + private func importFile(at localURL: URL, toParentItemIdentifier parentItemIdentifier: String, domainIdentifier: NSFileProviderDomainIdentifier) async throws { + let getXPCPromise: Promise> = FileProviderXPCConnector.shared.getXPC(serviceName: .fileImporting, domainIdentifier: domainIdentifier) + try await withCheckedThrowingContinuation({ continuation in + getXPCPromise.then { xpc in + xpc.proxy.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier) + }.then { + continuation.resume() + }.catch { + continuation.resume(throwing: $0) + }.always { + FileProviderXPCConnector.shared.invalidateXPC(getXPCPromise) + } + }) + } +} + +extension SaveFileIntentResponse { + static var success: SaveFileIntentResponse { SaveFileIntentResponse(code: .success, userActivity: nil) } + + static func failure(_ error: Error) -> SaveFileIntentResponse { + return SaveFileIntentResponse(error: error) + } + + convenience init(error: Error) { + self.init(failureReason: error.localizedDescription) + } + + convenience init(failureReason: String) { + self.init(code: .failure, userActivity: nil) + self.failureReason = failureReason + } +} + +extension CloudProviderType { + var assetName: String { + switch self { + case .dropbox: + return "dropbox-vault" + case .googleDrive: + return "google-drive-vault" + case .oneDrive: + return "onedrive-vault" + case .pCloud: + return "pcloud-vault" + case .webDAV: + return "webdav-vault" + case let .localFileSystem(type): + return type.assetName + } + } +} + +extension LocalFileSystemType { + var assetName: String { + switch self { + case .custom: + return "file-provider-vault" + case .iCloudDrive: + return "icloud-drive-vault" + } + } +} + +extension INImage { + convenience init(type: CloudProviderType) { + self.init(named: type.assetName) + } +} + +extension SaveFileIntent { + var shouldIgnoreExisting: Bool { + return ignoreExisting as? Bool ?? false + } +} + +extension FileManager { + var appGroupCacheDirectory: URL? { + let appGroupDirectory = containerURL(forSecurityApplicationGroupIdentifier: CryptomatorConstants.appGroupName) + return appGroupDirectory?.appendingPathComponent("Library/Caches") + } +} diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift index f1eea8c4c..af1fbe212 100644 --- a/FileProviderExtension/FileProviderExtension.swift +++ b/FileProviderExtension/FileProviderExtension.swift @@ -262,6 +262,7 @@ class FileProviderExtension: NSFileProviderExtension { if let domain = domain, let localURLProvider = localURLProvider, let dbPath = dbPath, let notificator = notificator { serviceSources.append(VaultUnlockingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) serviceSources.append(UploadRetryingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) + serviceSources.append(FileImportingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) } #endif let cacheManagingServiceSource = CacheManagingServiceSource(notificator: notificator) diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index 8df12af0b..6c1b624e3 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Unlock"; "fileProvider.clearFileFromCache.title" = "Clear File from Cache"; "fileProvider.clearFileFromCache.message" = "This only removes the local file from your device and does not delete the file in the cloud."; +"fileProvider.fileImporting.error.missingPremium" = "Unlock the full version in the Cryptomator app to gain write access to your vaults."; "fileProvider.uploadProgress.connecting" = "Connecting…"; "fileProvider.uploadProgress.message" = "Current Progress: %@\n\nIf you're noticing that the upload progress is stuck, you can retry the upload."; "fileProvider.uploadProgress.missing" = "Progress could not be determined. It may still be running in the background."; "fileProvider.uploadProgress.title" = "Uploading…"; "fileProvider.uploadProgress.missingDomainError" = "Could not find domain."; +"getFolderIntent.error.missingPath" = "No path was provided. Please provide a valid path for which a folder should be returned."; +"getFolderIntent.error.noVaultSelected" = "No vault has been selected."; +"intents.saveFile.missingFile" = "The provided file is not valid."; +"intents.saveFile.invalidFolder" = "The provided folder is not valid."; +"intents.saveFile.missingTemporaryFolder" = "Failed to create temporary folder."; +"intents.saveFile.lockedVault" = "You need to unlock your vault in order to use this shortcut."; +"intents.saveFile.selectedVaultNotFound" = "Selected vault could not be found."; + "keepUnlocked.alert.title" = "Lock Vault?"; "keepUnlocked.alert.message" = "This change requires your vault to be locked in order to take effect."; "keepUnlocked.alert.confirm" = "Confirm & Lock Now"; diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 2e2a15695..9f9f119bc 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -1,2 +1,2 @@ git_url(ENV["SKYMATIC_MATCH_GIT_URL"]) -app_identifier(["org.cryptomator.ios", "org.cryptomator.ios.fileprovider", "org.cryptomator.ios.fileprovider-ui"]) +app_identifier(["org.cryptomator.ios", "org.cryptomator.ios.fileprovider", "org.cryptomator.ios.fileprovider-ui", "org.cryptomator.ios.intents"]) From b639b1f0ed1d4d74ead74fd41f1087eb8fd60168 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Mon, 30 May 2022 14:04:19 +0200 Subject: [PATCH 04/15] Added link to the shortcuts guide in the settings screen --- Cryptomator/Settings/SettingsCoordinator.swift | 6 ++++++ Cryptomator/Settings/SettingsViewController.swift | 2 ++ Cryptomator/Settings/SettingsViewModel.swift | 2 ++ SharedResources/en.lproj/Localizable.strings | 1 + 4 files changed, 11 insertions(+) diff --git a/Cryptomator/Settings/SettingsCoordinator.swift b/Cryptomator/Settings/SettingsCoordinator.swift index 9bd72434b..91539e4a1 100644 --- a/Cryptomator/Settings/SettingsCoordinator.swift +++ b/Cryptomator/Settings/SettingsCoordinator.swift @@ -69,6 +69,12 @@ class SettingsCoordinator: Coordinator { } } + func openShortcutsGuide() { + if let shortcutsGuideURL = URL(string: "https://docs.cryptomator.org/en/1.6/ios/shortcuts-guide/") { + UIApplication.shared.open(shortcutsGuideURL) + } + } + func showUnlockFullVersion() { let child = SettingsPurchaseCoordinator(navigationController: navigationController) childCoordinators.append(child) // TODO: remove missing? diff --git a/Cryptomator/Settings/SettingsViewController.swift b/Cryptomator/Settings/SettingsViewController.swift index 3a5682724..151850e53 100644 --- a/Cryptomator/Settings/SettingsViewController.swift +++ b/Cryptomator/Settings/SettingsViewController.swift @@ -129,6 +129,8 @@ class SettingsViewController: StaticUITableViewController { viewModel.restorePurchase().then { [weak self] _ in self?.refreshRows() } + case .showShortcutsGuide: + coordinator?.openShortcutsGuide() case .none: break } diff --git a/Cryptomator/Settings/SettingsViewModel.swift b/Cryptomator/Settings/SettingsViewModel.swift index 14692f936..3fa630c9f 100644 --- a/Cryptomator/Settings/SettingsViewModel.swift +++ b/Cryptomator/Settings/SettingsViewModel.swift @@ -20,6 +20,7 @@ enum SettingsButtonAction: String { case showCloudServices case showContact case showRateApp + case showShortcutsGuide case showUnlockFullVersion case showManageSubscriptions case restorePurchase @@ -61,6 +62,7 @@ class SettingsViewModel: TableViewModel { ButtonCellViewModel(action: .sendLogFile, title: LocalizedString.getValue("settings.sendLogFile")) ]), Section(id: .miscSection, elements: [ + ButtonCellViewModel(action: SettingsButtonAction.showShortcutsGuide, title: LocalizedString.getValue("settings.shortcutsGuide")), ButtonCellViewModel(action: SettingsButtonAction.showContact, title: LocalizedString.getValue("settings.contact")), ButtonCellViewModel(action: SettingsButtonAction.showRateApp, title: LocalizedString.getValue("settings.rateApp")) ]) diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index 6c1b624e3..b34109156 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -189,6 +189,7 @@ "settings.manageSubscriptions" = "Manage Subscription"; "settings.rateApp" = "Rate App"; "settings.sendLogFile" = "Send Log File"; +"settings.shortcutsGuide" = "Shortcuts Guide"; "settings.unlockFullVersion" = "Unlock Full Version"; "snapshots.fileprovider.file1" = "/Accounting.numbers"; From f5244f91e53daadf309f6e4a0411518f5cd78de5 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Mon, 30 May 2022 14:43:06 +0200 Subject: [PATCH 05/15] Changed the log level from debug to info inside the `WorkflowDependencyFactory` --- .../Workflow/WorkflowDependencyFactory.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CryptomatorFileProvider/Workflow/WorkflowDependencyFactory.swift b/CryptomatorFileProvider/Workflow/WorkflowDependencyFactory.swift index 9e3387b78..56b706ce6 100644 --- a/CryptomatorFileProvider/Workflow/WorkflowDependencyFactory.swift +++ b/CryptomatorFileProvider/Workflow/WorkflowDependencyFactory.swift @@ -85,8 +85,7 @@ class WorkflowDependencyFactory { } let lock: Promise = all(dependencies).then { _ -> Void in // no-op - #warning("TODO: Change log level to info after next TestFlight release") - DDLogDebug("acquired lock for path: \(path) - type: \(type)") + DDLogInfo("acquired lock for path: \(path) - type: \(type)") } addToCollection(lock, path: path, type: type) lock.always { @@ -103,8 +102,7 @@ class WorkflowDependencyFactory { private func createUnlock(path: CloudPath, type: LockType, basedOn child: Promise) { let unlock = child.then { // no-op - #warning("TODO: Change log level to info after next TestFlight release") - DDLogDebug("released lock for path: \(path) - type: \(type)") + DDLogInfo("released lock for path: \(path) - type: \(type)") } addToCollection(unlock, path: path, type: type) unlock.always { From 3355b1d7d79ce5d87d1d336e877036c40aaf0eef Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Mon, 30 May 2022 18:10:16 +0200 Subject: [PATCH 06/15] Removed cleanup of old database --- .../CryptomatorDatabase.swift | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift index 253b5c05c..3b31cc14c 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift @@ -34,8 +34,6 @@ public class CryptomatorDatabase { } static var migrator: DatabaseMigrator { - CryptomatorDatabase.cleanupOldDatabase() - var migrator = DatabaseMigrator() migrator.registerMigration("v1") { db in try v1Migration(db) @@ -187,19 +185,4 @@ public class CryptomatorDatabase { } } } - - /** - Removes the old shared database (prior 2.0.0-beta9). - - The old shared database prior 2.0.0-beta9 does not support the new CloudProviderType with associated values for WebDAV and LocalFileSystem. - Instead of simply nuking the existing database, we remove the old database (and change the path of the new database) as this also resolves issues when upgrading from an older testflight version to the release version. - This function will be removed in the future. - */ - private static func cleanupOldDatabase() { - #warning("TODO (after April 2022): Remove this function") - guard let url = oldSharedDBURL else { - return - } - try? FileManager.default.removeItem(at: url) - } } From f055447ece1169177ef439d4cec564ac7918c26b Mon Sep 17 00:00:00 2001 From: Cryptobot Date: Tue, 31 May 2022 15:49:48 +0200 Subject: [PATCH 07/15] New Crowdin updates (#223) [ci skip] --- SharedResources/cs.lproj/Localizable.strings | 42 +++++++++++++++++++ SharedResources/de.lproj/Localizable.strings | 13 +++++- SharedResources/el.lproj/Localizable.strings | 10 +++++ SharedResources/es.lproj/Localizable.strings | 10 +++++ SharedResources/fr.lproj/Localizable.strings | 10 +++++ SharedResources/he.lproj/Localizable.strings | 4 ++ SharedResources/it.lproj/Localizable.strings | 1 + SharedResources/nl.lproj/Localizable.strings | 10 +++++ SharedResources/pl.lproj/Localizable.strings | 38 ++++++++++------- .../pt-BR.lproj/Localizable.strings | 10 +++++ SharedResources/ru.lproj/Localizable.strings | 9 ++++ SharedResources/si.lproj/Localizable.strings | 8 ++++ SharedResources/sk.lproj/Localizable.strings | 10 +++++ .../sw-TZ.lproj/Localizable.strings | 9 ++++ SharedResources/tr.lproj/Localizable.strings | 12 +++++- .../zh-Hans.lproj/Localizable.strings | 10 +++++ 16 files changed, 190 insertions(+), 16 deletions(-) create mode 100644 SharedResources/si.lproj/Localizable.strings diff --git a/SharedResources/cs.lproj/Localizable.strings b/SharedResources/cs.lproj/Localizable.strings index 3ffce3972..ec58a8235 100644 --- a/SharedResources/cs.lproj/Localizable.strings +++ b/SharedResources/cs.lproj/Localizable.strings @@ -10,6 +10,7 @@ "common.button.cancel" = "Zrušit"; "common.button.change" = "Změnit"; "common.button.choose" = "Vybrat"; +"common.button.clear" = "Smazat"; "common.button.close" = "Zavřít"; "common.button.confirm" = "Potvrdit"; "common.button.create" = "Vytvořit"; @@ -37,6 +38,7 @@ "addVault.title" = "Přidat trezor"; "addVault.createNewVault.title" = "Vytvořit nový trezor"; +"addVault.createNewVault.purchase" = "Vytvoření nového trezoru vyžaduje plnou verzi Cryptomatoru."; "addVault.createNewVault.setVaultName.header.title" = "Zvolte jméno trezoru."; "addVault.createNewVault.setVaultName.cells.name" = "Název trezoru"; "addVault.createNewVault.setVaultName.error.emptyVaultName" = "Je potřeba zadat název trezoru."; @@ -96,6 +98,13 @@ "fileProvider.error.defaultLock.title" = "Požadováno odemknutí"; "fileProvider.error.defaultLock.message" = "Pro přístup a zobrazení obsahu vašeho trezoru musí být trezor odemčen."; "fileProvider.error.unlockButton" = "Odemknout"; +"fileProvider.clearFileFromCache.title" = "Smazat soubor z mezipaměti"; +"fileProvider.clearFileFromCache.message" = "Pouze smaže soubor z Vašeho zařízení a ponechá soubor v cloudu."; +"fileProvider.uploadProgress.connecting" = "Připojování…"; +"fileProvider.uploadProgress.message" = "Aktuální průběh: %@\n\nPokud se domníváte, že proces nahrávání je zaseknutý, můžete zkusit nahrávání znovu."; +"fileProvider.uploadProgress.missing" = "Průběh nebylo možné zjistit. Může nadále pokračovat na pozadí."; +"fileProvider.uploadProgress.title" = "Nahrávání…"; +"fileProvider.uploadProgress.missingDomainError" = "Doména nebyla nalezena."; "keepUnlocked.alert.title" = "Zamknout trezor?"; "keepUnlocked.alert.message" = "Tato změna vyžaduje uzamčení trezoru pro projevení."; @@ -126,17 +135,38 @@ "onboarding.button.continue" = "Pokračovat"; "purchase.beginFreeTrial.alert.title" = "Zkušební verze odemčena"; +"purchase.expiredTrial" = "Vaše zkušební verze vypršela."; +"purchase.footer.privacyPolicy" = "Ochrana osobních údajů"; +"purchase.footer.termsOfUse" = "Podmínky používání"; +"purchase.header.feature.familySharing" = "Rodinné sdílení"; +"purchase.header.feature.openSource" = "Vývoj Open-source"; +"purchase.header.feature.writeAccess" = "Zápis do vašeho trezoru"; +"purchase.product.donateAndUpgrade" = "Přispějte a upgradujte"; +"purchase.product.freeUpgrade" = "Bezplatný upgrade"; +"purchase.product.lifetimeLicense" = "Doživotní licence"; +"purchase.product.lifetimeLicense.duration" = "jednorázově"; +"purchase.product.pricing.free" = "Zdarma"; +"purchase.product.trial" = "30denní zkušební verze"; +"purchase.product.trial.expirationDate" = "Datum expirace: %@"; +"purchase.product.trial.duration" = "na 30 dní"; +"purchase.product.yearlySubscription" = "Roční předplatné"; +"purchase.product.yearlySubscription.duration" = "ročně"; +"purchase.readOnlyMode.alert.title" = "Režim jen pro čtení"; +"purchase.readOnlyMode.alert.message" = "Plnou verzi Cryptomatoru můžete později odemknout v nastavení a pro tuto chvíli použít Cryptomator v režimu jen pro čtení."; "purchase.restorePurchase.button" = "Obnovit nákup"; "purchase.restorePurchase.validTrialFound.alert.title" = "Zkušební verze pokračuje"; "purchase.restorePurchase.validTrialFound.alert.message" = "Nyní můžete použít plnou verzi Cryptomatoru na omezenou dobu. Vaše zkušební verze vyprší %@. Poté budou vaše trezory stále dostupné v režimu jen pro čtení."; "purchase.restorePurchase.fullVersionFound.alert.title" = "Obnovení bylo úspěšné"; "purchase.restorePurchase.fullVersionNotFound.alert.title" = "Žádná plná verze"; "purchase.restorePurchase.fullVersionNotFound.alert.message" = "Nebyli jsme schopni najít dříve zakoupenou plnou verzi, která by mohla být obnovena. Zkuste prosím jinou možnost."; +"purchase.restorePurchase.eligibleForUpgrade.alert.title" = "Upgrade je dostupný"; +"purchase.restorePurchase.eligibleForUpgrade.alert.message" = "Zdá se, že se pokoušíte upgradovat ze starší verze Cryptomatoru. V takovém případě prosím vyberte možnost \"Nabídka na Upgrade\"."; "purchase.retry.button" = "Opakovat"; "purchase.retry.footer" = "Nelze načíst dostupné produkty."; "purchase.title" = "Odemknout plnou verzi"; "purchase.unlockedFullVersion.message" = "Nyní můžete použít plnou verzi Cryptomatoru. Šťastné šifrování!"; "purchase.unlockedFullVersion.title" = "Děkuji"; +"purchase.error.unknown" = "Tento nákup není z neznámého důvodu v App Store dostupný. Opakujte, prosím, akci později.\n\nPokud tato chyba přetrvává, zkuste restartovat Vaše zařízení nebo se odhlásit a přihlásit znovu k Vašemu Apple ID v nastavení iOS."; "settings.title" = "Nastavení"; "settings.aboutCryptomator" = "O Cryptomatoru"; @@ -165,6 +195,9 @@ "snapshots.main.vault3" = "/Dokumenty"; "snapshots.main.vault4" = "/výlet do Kalifornie"; +"trialStatus.active" = "Aktivní"; +"trialStatus.expired" = "Expirováno"; + "unlockVault.button.unlock" = "Odemknout"; "unlockVault.button.unlockVia" = "Odemknout pomocí %@"; "unlockVault.password.footer" = "Zadejte heslo pro \"%@\"."; @@ -177,8 +210,11 @@ "untrustedTLSCertificate.message" = "TLS certifikát \"%@\" je neplatný. Chcete mu přesto důvěřovat?\n\n SHA-256: %@"; "untrustedTLSCertificate.add" = "Důvěřovat"; "untrustedTLSCertificate.dismiss" = "Nedůvěřovat"; + +"upgrade.title" = "Nabídka na Upgrade"; "upgrade.notEligible.alert.title" = "Upgrade se nezdařil"; "upgrade.notEligible.alert.message" = "Cryptomator nemohl najít starší verzi nainstalovanou na vašem zařízení. Pokud jste si ji zakoupili, stáhněte si ji prosím znovu z App Store a zkuste to znovu."; +"upgrade.info" = "Děkujeme, že důvěřujete Cryptomatoru od první verze. Jako věrný uživatel máte nárok na bezplatný upgrade."; "urlSession.error.httpError.401" = "Nesprávné uživatelské jméno a/nebo heslo."; "urlSession.error.httpError.403" = "Nedostatečná práva k požadovanému zdroji."; @@ -226,3 +262,9 @@ "webDAVAuthentication.httpConnection.alert.message" = "Použití HTTP je nezabezpečené. Doporučujeme použít HTTPS. Pokud znáte rizika, můžete pokračovat s HTTP."; "webDAVAuthentication.httpConnection.change" = "Změnit na HTTPS"; "webDAVAuthentication.httpConnection.continue" = "Zachovat HTTP"; + +"webDAVAuthenticator.error.unsupportedProtocol" = "Server není kompatibilní s WebDAV. Zkontrolujte, zda jste použili správnou adresu."; +"webDAVAuthenticator.error.untrustedCertificate" = "Certifikát tohoto serveru není důvěryhodný. Možná budete muset znovu přidat toto WebDAV připojení."; + +"Retry Upload" = "Opakovat nahrání"; +"Clear from Cache" = "Smazat z mezipaměti"; diff --git a/SharedResources/de.lproj/Localizable.strings b/SharedResources/de.lproj/Localizable.strings index c70b30c10..37c69cea2 100644 --- a/SharedResources/de.lproj/Localizable.strings +++ b/SharedResources/de.lproj/Localizable.strings @@ -100,11 +100,21 @@ "fileProvider.error.unlockButton" = "Entsperren"; "fileProvider.clearFileFromCache.title" = "Datei aus Zwischenspeicher entfernen"; "fileProvider.clearFileFromCache.message" = "Dies entfernt nur die lokale Datei von Ihrem Gerät und löscht nicht die Datei in der Cloud."; +"fileProvider.fileImporting.error.missingPremium" = "Schalten Sie die Vollversion in der Cryptomator-App frei, um Schreibzugriff auf Ihre Tresore zu erhalten."; "fileProvider.uploadProgress.connecting" = "Verbindung wird hergestellt …"; +"fileProvider.uploadProgress.message" = "Aktueller Fortschritt: %@\n\nFalls du feststellst, dass der Upload feststeckt, kannst du den Upload erneut versuchen."; "fileProvider.uploadProgress.missing" = "Der Fortschritt konnte nicht ermittelt werden. Möglicherweise läuft der Upload noch im Hintergrund."; "fileProvider.uploadProgress.title" = "Wird hochgeladen …"; "fileProvider.uploadProgress.missingDomainError" = "Domain konnte nicht gefunden werden."; +"getFolderIntent.error.missingPath" = "Es wurde kein Pfad angegeben. Bitte geben Sie einen gültigen Pfad an, für den ein Ordner angegeben werden soll."; +"getFolderIntent.error.noVaultSelected" = "Kein Tresor ausgewählt."; +"intents.saveFile.missingFile" = "Die angegebene Datei ist ungültig."; +"intents.saveFile.invalidFolder" = "Der angegebene Ordner ist ungültig."; +"intents.saveFile.missingTemporaryFolder" = "Erstellung eines temporären Ordners fehlgeschlagen."; +"intents.saveFile.lockedVault" = "Sie müssen Ihren Tresor entsperren, um diese Verknüpfung nutzen zu können."; +"intents.saveFile.selectedVaultNotFound" = "Ausgewählter Tresor konnte nicht gefunden werden."; + "keepUnlocked.alert.title" = "Tresor sperren?"; "keepUnlocked.alert.message" = "Damit diese Änderung wirksam werden kann, muss dein Tresor gesperrt werden."; "keepUnlocked.alert.confirm" = "Bestätigen & Jetzt sperren"; @@ -177,8 +187,9 @@ "settings.debugMode" = "Debug Modus"; "settings.debugMode.alert.message" = "In diesem Modus könnten sensible Daten in eine Log-Datei auf diesem Gerät geschrieben werden (z.B. Dateinamen und Pfade). Ausgeschlossen davon sind u.a. Passwörter und Cookies.\n\nDenke daran, den Debug-Modus so schnell wie möglich wieder zu deaktivieren."; "settings.manageSubscriptions" = "Abonnement verwalten"; -"settings.rateApp" = "App Bewerten"; +"settings.rateApp" = "App bewerten"; "settings.sendLogFile" = "Protokolldatei senden"; +"settings.shortcutsGuide" = "Kurzbefehle"; "settings.unlockFullVersion" = "Vollversion freischalten"; "snapshots.fileprovider.file1" = "/Buchhaltung.numbers"; diff --git a/SharedResources/el.lproj/Localizable.strings b/SharedResources/el.lproj/Localizable.strings index cdd1c9d46..c8c2b68ea 100644 --- a/SharedResources/el.lproj/Localizable.strings +++ b/SharedResources/el.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Ξεκλείδωμα"; "fileProvider.clearFileFromCache.title" = "Εκκαθάριση αρχείου από την προσωρινή μνήμη"; "fileProvider.clearFileFromCache.message" = "Αυτό καταργεί μόνο το τοπικό αρχείο από τη συσκευή σας και δεν διαγράφει το αρχείο στο cloud."; +"fileProvider.fileImporting.error.missingPremium" = "Ξεκλειδώστε την πλήρη έκδοση στην εφαρμογή Cryptomator για να αποκτήσετε πρόσβαση εγγραφής στις κρύπτες σας."; "fileProvider.uploadProgress.connecting" = "Σύνδεση…"; "fileProvider.uploadProgress.message" = "Τρέχουσα Πρόοδος: %@%\n\nΑν παρατηρήσετε ότι η πρόοδος μεταφόρτωσης έχει κολλήσει, μπορείτε να δοκιμάσετε ξανά τη μεταφόρτωση."; "fileProvider.uploadProgress.missing" = "Η πρόοδος δεν μπορεί να καθοριστεί. Μπορεί να εκτελείται στο παρασκήνιο."; "fileProvider.uploadProgress.title" = "Μεταφόρτωση…"; "fileProvider.uploadProgress.missingDomainError" = "Δεν ήταν δυνατή η εύρεση του τομέα."; +"getFolderIntent.error.missingPath" = "Δε δόθηκε καμία διαδρομή. Παρακαλώ δώστε μια έγκυρη διαδρομή για την οποία θα πρέπει να επιστραφεί ένας φάκελος."; +"getFolderIntent.error.noVaultSelected" = "Δεν έχει επιλεγεί κρύπτη."; +"intents.saveFile.missingFile" = "Το παρεχόμενο αρχείο δεν είναι έγκυρο."; +"intents.saveFile.invalidFolder" = "Ο παρεχόμενος φάκελος δεν είναι έγκυρος."; +"intents.saveFile.missingTemporaryFolder" = "Αποτυχία δημιουργίας προσωρινού φακέλου."; +"intents.saveFile.lockedVault" = "Πρέπει να ξεκλειδώσετε την κρύπτη σας για να χρησιμοποιήσετε αυτήν τη συντόμευση."; +"intents.saveFile.selectedVaultNotFound" = "Δεν ήταν δυνατή η εύρεση της επιλεγμένης κρύπτης."; + "keepUnlocked.alert.title" = "Κλείδωμα Κρύπτης;"; "keepUnlocked.alert.message" = "Αυτή η αλλαγή απαιτεί η κρύπτη σας να κλειδωθεί για να τεθεί σε ισχύ."; "keepUnlocked.alert.confirm" = "Επιβεβαίωση & Κλείδωμα Τώρα"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Διαχείριση Συνδρομής"; "settings.rateApp" = "Αξιολογήστε την Εφαρμογή"; "settings.sendLogFile" = "Αποστολή Αρχείου Καταγραφής"; +"settings.shortcutsGuide" = "Οδηγός Συντομεύσεων"; "settings.unlockFullVersion" = "Ξεκλείδωμα Πλήρους Έκδοσης"; "snapshots.fileprovider.file1" = "/Λογιστική.αριθμοί"; diff --git a/SharedResources/es.lproj/Localizable.strings b/SharedResources/es.lproj/Localizable.strings index 035e46e41..4b7d7a8d7 100644 --- a/SharedResources/es.lproj/Localizable.strings +++ b/SharedResources/es.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Desbloquear"; "fileProvider.clearFileFromCache.title" = "Borrar archivo de la caché"; "fileProvider.clearFileFromCache.message" = "Esto solo elimina el archivo local de su dispositivo y no lo elimina de la nube."; +"fileProvider.fileImporting.error.missingPremium" = "Desbloquee la versión completa en la aplicación de Cryptomator para obtener acceso de escritura a sus bóvedas."; "fileProvider.uploadProgress.connecting" = "Conectando…"; "fileProvider.uploadProgress.message" = "Progreso actual: %@\n\nSi nota que el progreso de la carga está atascado, puede volver a intentar la carga."; "fileProvider.uploadProgress.missing" = "No se pudo determinar el progreso. Puede que todavía se ejecute en segundo plano."; "fileProvider.uploadProgress.title" = "Cargando…"; "fileProvider.uploadProgress.missingDomainError" = "No se encontró el dominio."; +"getFolderIntent.error.missingPath" = "No se ha proporcionado ninguna ruta. Proporcione una ruta válida para la que se debe devolver una carpeta."; +"getFolderIntent.error.noVaultSelected" = "No se ha seleccionado una bóveda."; +"intents.saveFile.missingFile" = "El archivo proporcionado es inválido."; +"intents.saveFile.invalidFolder" = "La carpeta proporcionada es inválida."; +"intents.saveFile.missingTemporaryFolder" = "Error al crear la carpeta temporal."; +"intents.saveFile.lockedVault" = "Necesita desbloquear su bóveda para poder usar este acceso directo."; +"intents.saveFile.selectedVaultNotFound" = "No se pudo encontrar la bóveda seleccionada."; + "keepUnlocked.alert.title" = "¿Bloquear bóveda?"; "keepUnlocked.alert.message" = "Este cambio requiere que su bóveda esté bloqueada para que surta efecto."; "keepUnlocked.alert.confirm" = "Confirmar y bloquear ahora"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Gestionar suscripción"; "settings.rateApp" = "Calificar app"; "settings.sendLogFile" = "Enviar archivo de registro"; +"settings.shortcutsGuide" = "Guía de accesos directos"; "settings.unlockFullVersion" = "Desbloquear versión completa"; "snapshots.fileprovider.file1" = "/Números.contables"; diff --git a/SharedResources/fr.lproj/Localizable.strings b/SharedResources/fr.lproj/Localizable.strings index b04be6753..2029ed308 100644 --- a/SharedResources/fr.lproj/Localizable.strings +++ b/SharedResources/fr.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Déverrouiller"; "fileProvider.clearFileFromCache.title" = "Effacer le fichier du cache"; "fileProvider.clearFileFromCache.message" = "Cela ne supprime le fichier local que de votre appareil et ne supprime pas le fichier dans le cloud."; +"fileProvider.fileImporting.error.missingPremium" = "Débloquez la version complète dans l'application Cryptomator pour obtenir un accès en écriture à vos coffres."; "fileProvider.uploadProgress.connecting" = "Connexion…"; "fileProvider.uploadProgress.message" = "Progression actuelle: %@\n\nSi vous remarquez que la progression de l'envoi est bloquée, vous pouvez recommencer l'envoi."; "fileProvider.uploadProgress.missing" = "La progression n'a pas pu être déterminée. Il se peut que cela soit encore en cours en arrière-plan."; "fileProvider.uploadProgress.title" = "Envoi en cours…"; "fileProvider.uploadProgress.missingDomainError" = "Impossible de trouver le domaine."; +"getFolderIntent.error.missingPath" = "Aucun chemin n'a été fourni. Veuillez fournir un chemin d'accès valide pour lequel un dossier doit être renvoyé."; +"getFolderIntent.error.noVaultSelected" = "Aucun coffre n'a été sélectionné."; +"intents.saveFile.missingFile" = "Le fichier fourni n'est pas valide."; +"intents.saveFile.invalidFolder" = "Le dossier fourni n'est pas valide."; +"intents.saveFile.missingTemporaryFolder" = "Impossible de créer un dossier temporaire."; +"intents.saveFile.lockedVault" = "Vous devez déverrouiller votre coffre pour utiliser ce raccourci."; +"intents.saveFile.selectedVaultNotFound" = "Le coffre sélectionné est introuvable."; + "keepUnlocked.alert.title" = "Verrouiller le coffre ?"; "keepUnlocked.alert.message" = "Cette modification nécessite que votre coffre soit verrouillé."; "keepUnlocked.alert.confirm" = "Confirmer & Verrouiller maintenant"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Gérer l'abonnement"; "settings.rateApp" = "Évaluer l'application"; "settings.sendLogFile" = "Envoyer le fichier journal"; +"settings.shortcutsGuide" = "Guide des raccourcis"; "settings.unlockFullVersion" = "Débloquer la version complète"; "snapshots.fileprovider.file1" = "Compte.numéros"; diff --git a/SharedResources/he.lproj/Localizable.strings b/SharedResources/he.lproj/Localizable.strings index 021e3c608..c7635f819 100644 --- a/SharedResources/he.lproj/Localizable.strings +++ b/SharedResources/he.lproj/Localizable.strings @@ -51,6 +51,9 @@ "fileProvider.uploadProgress.missing" = "לא ניתן לקבוע את תהליך ההתקדמות. ייתכן והתהליך עדיין רץ ברקע."; "fileProvider.uploadProgress.title" = "העלאה…"; "fileProvider.uploadProgress.missingDomainError" = "לא ניתן למצוא את הדומיין."; +"getFolderIntent.error.noVaultSelected" = "לא נבחר קובץ vault."; +"intents.saveFile.invalidFolder" = "התיקייה שנבחרה איננה תקינה."; +"intents.saveFile.selectedVaultNotFound" = "ה vault שנבחר לא נמצא."; "onboarding.title" = "ברוך הבא"; "onboarding.button.continue" = "המשך"; @@ -59,6 +62,7 @@ "settings.title" = "הגדרות"; "settings.clearCache" = "נקה מטמון"; +"settings.shortcutsGuide" = "מדריך קיצורי דרך"; "unlockVault.button.unlock" = "בטל נעילה"; diff --git a/SharedResources/it.lproj/Localizable.strings b/SharedResources/it.lproj/Localizable.strings index ea587fa15..58a681b7b 100644 --- a/SharedResources/it.lproj/Localizable.strings +++ b/SharedResources/it.lproj/Localizable.strings @@ -101,6 +101,7 @@ "fileProvider.clearFileFromCache.title" = "Cancella file dalla cache"; "fileProvider.clearFileFromCache.message" = "Questo rimuove solo il file locale dal dispositivo e non elimina il file nel cloud."; "fileProvider.uploadProgress.connecting" = "Connessione…"; +"fileProvider.uploadProgress.message" = "Avanzamento attuale: %@\n\nSe stai notando che l'avanzamento di caricamento è bloccato, puoi riprovare il caricamento."; "fileProvider.uploadProgress.missing" = "Non è stato possibile determinare i progressi, che potrebbero essere ancora in esecuzione in background."; "fileProvider.uploadProgress.title" = "Caricamento…"; "fileProvider.uploadProgress.missingDomainError" = "Impossibile trovare il dominio."; diff --git a/SharedResources/nl.lproj/Localizable.strings b/SharedResources/nl.lproj/Localizable.strings index a435a2033..ecc2f3526 100644 --- a/SharedResources/nl.lproj/Localizable.strings +++ b/SharedResources/nl.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Ontgrendel"; "fileProvider.clearFileFromCache.title" = "Wissen uit cache"; "fileProvider.clearFileFromCache.message" = "Dit verwijdert alleen het lokale bestand van uw apparaat en verwijdert het bestand in de cloud niet."; +"fileProvider.fileImporting.error.missingPremium" = "Ontgrendel de volledige versie in de Cryptomator-app om schrijftoegang te krijgen tot uw kluizen."; "fileProvider.uploadProgress.connecting" = "Verbinden…"; "fileProvider.uploadProgress.message" = "Huidige vooruitgang: %@\n\n Als je merkt dat de upload vastzit, dan kan je deze opnieuw proberen."; "fileProvider.uploadProgress.missing" = "De vooruitgang kon niet worden bepaald. Misschien wordt deze nog steeds op de achtergrond uitgevoerd."; "fileProvider.uploadProgress.title" = "Uploaden…"; "fileProvider.uploadProgress.missingDomainError" = "Kan domein niet vinden."; +"getFolderIntent.error.missingPath" = "Er is geen pad opgegeven. Geef een geldig pad om de folder weer te geven."; +"getFolderIntent.error.noVaultSelected" = "Er is geen kluis geselecteerd."; +"intents.saveFile.missingFile" = "Het opgegeven bestand is niet geldig."; +"intents.saveFile.invalidFolder" = "De opgegeven map is niet geldig."; +"intents.saveFile.missingTemporaryFolder" = "Aanmaken van tijdelijke map mislukt."; +"intents.saveFile.lockedVault" = "Je moet je kluis ontgrendelen om deze snelkoppeling te gebruiken."; +"intents.saveFile.selectedVaultNotFound" = "Geselecteerde kluis kon niet worden gevonden."; + "keepUnlocked.alert.title" = "Kluis vergrendelen?"; "keepUnlocked.alert.message" = "Deze wijziging vereist dat je kluis vergrendeld is om van kracht te worden."; "keepUnlocked.alert.confirm" = "Bevestig en vergrendel nu"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Abonnement beheren"; "settings.rateApp" = "Beoordeel App"; "settings.sendLogFile" = "Logbestand verzenden"; +"settings.shortcutsGuide" = "Snelkoppelingen Gids"; "settings.unlockFullVersion" = "Volledige versie ontgrendelen"; "snapshots.fileprovider.file1" = "/Rekening.getallen"; diff --git a/SharedResources/pl.lproj/Localizable.strings b/SharedResources/pl.lproj/Localizable.strings index f07f9c296..7d523a628 100644 --- a/SharedResources/pl.lproj/Localizable.strings +++ b/SharedResources/pl.lproj/Localizable.strings @@ -51,7 +51,7 @@ "addVault.createNewVault.password.confirmPassword.alert.message" = "WAŻNE: Jeśli zapomnisz hasła, nie ma możliwości odzyskania danych."; "addVault.createNewVault.password.error.emptyPassword" = "Hasło nie może być puste."; "addVault.createNewVault.password.error.nonMatchingPasswords" = "Hasła nie są identyczne."; -"addVault.createNewVault.password.error.tooShortPassword" = "Hasło musi zawierać co najmniej 8 znaków"; +"addVault.createNewVault.password.error.tooShortPassword" = "Hasło musi zawierać co najmniej 8 znaków."; "addVault.createNewVault.progress" = "Tworzenie sejfu…"; "addVault.openExistingVault.title" = "Otwórz istniejący sejf"; "addVault.openExistingVault.chooseCloud.header" = "Gdzie znajduje się sejf?"; @@ -69,22 +69,22 @@ "changePassword.header.currentPassword.title" = "Wprowadź obecne hasło."; "changePassword.header.newPassword.title" = "Wprowadź nowe hasło."; "changePassword.header.newPasswordConfirmation.title" = "Potwierdź nowe hasło."; -"changePassword.progress" = "Zmiana hasła…"; +"changePassword.progress" = "Zmienianie hasła…"; "chooseFolder.emptyFolder.footer" = "Folder jest pusty"; "chooseFolder.createNewFolder.header.title" = "Wybierz nazwę katalogu."; "chooseFolder.createNewFolder.cells.name" = "Nazwa katalogu"; -"chooseFolder.createNewFolder.error.emptyFolderName" = "Nazwa folderu nie może być pusta"; +"chooseFolder.createNewFolder.error.emptyFolderName" = "Nazwa folderu nie może być pusta."; "chooseFolder.createNewFolder.progress" = "Tworzenie katalogu…"; -"cloudProvider.error.itemNotFound" = "'%s' nie został odnaleziony."; -"cloudProvider.error.itemAlreadyExists" = "już istnieje."; +"cloudProvider.error.itemNotFound" = "\"%@\" nie został odnaleziony."; +"cloudProvider.error.itemAlreadyExists" = "\"%@\" już istnieje."; "cloudProvider.error.itemTypeMismatch" = "\"%@\" ma nieoczekiwany typ przedmiotu."; "cloudProvider.error.parentFolderDoesNotExist" = "Folder nadrzędny \"%@\" nie istnieje."; "cloudProvider.error.pageTokenInvalid" = "Pobieranie zawartości katalogu nie może być kontynuowane."; -"cloudProvider.error.quotaInsufficient" = "Pamięć nie ma wystarczającej ilości miejsca."; +"cloudProvider.error.quotaInsufficient" = "Brak wystarczającej ilości miejsca w danej lokalizacji."; "cloudProvider.error.unauthorized" = "Nie można wykonać operacji nieautoryzowanej."; -"cloudProvider.error.noInternetConnection" = "Połączenie z Internetem potrzebne do tej operacji."; +"cloudProvider.error.noInternetConnection" = "Połączenie z Internetem jest wymagane do wykonania tej operacji."; "cloudProviderType.localFileSystem" = "Inny dostawca plików"; @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Odblokuj"; "fileProvider.clearFileFromCache.title" = "Usuń plik z pamięci podręcznej"; "fileProvider.clearFileFromCache.message" = "Powoduje to jedynie usunięcie pliku lokalnego z urządzenia, a nie usunięcie pliku w chmurze."; -"fileProvider.uploadProgress.connecting" = "Łączenie..."; +"fileProvider.fileImporting.error.missingPremium" = "Odblokuj pełną wersję aplikacji Cryptomator, aby uzyskać możliwość zapisu w sejfach."; +"fileProvider.uploadProgress.connecting" = "Łączenie…"; "fileProvider.uploadProgress.message" = "Bieżący postęp: %@\n\nJeśli zauważysz, że postęp przesyłania zawiesił się, możesz spróbować ponownie przesłać dane."; "fileProvider.uploadProgress.missing" = "Nie można zweryfikować postępu. Może on nadal działać w tle."; -"fileProvider.uploadProgress.title" = "Przesyłanie..."; +"fileProvider.uploadProgress.title" = "Przesyłanie…"; "fileProvider.uploadProgress.missingDomainError" = "Nie można odnaleźć domeny."; +"getFolderIntent.error.missingPath" = "Nie podano ścieżki. Proszę podać prawidłową ścieżkę do folderu."; +"getFolderIntent.error.noVaultSelected" = "Nie wybrano sejfu."; +"intents.saveFile.missingFile" = "Wskazany plik nie jest prawidłowy."; +"intents.saveFile.invalidFolder" = "Wskazany folder jest niewłaściwy."; +"intents.saveFile.missingTemporaryFolder" = "Nie udało się utworzyć katalogu tymczasowego."; +"intents.saveFile.lockedVault" = "Musisz odblokować sejf, aby użyć tego skrótu."; +"intents.saveFile.selectedVaultNotFound" = "Nie znaleziono wybranego sejfu."; + "keepUnlocked.alert.title" = "Zablokować sejf?"; "keepUnlocked.alert.message" = "Wprowadzenie tej zmiany wymaga zablokowania sejfu."; "keepUnlocked.alert.confirm" = "Potwierdź i zablokuj teraz"; @@ -124,7 +133,7 @@ "localFileSystemAuthentication.openExistingVault.error.noVaultFound" = "Wybrany folder nie jest sejfem. Spróbuj ponownie z innym katalogiem."; "localFileSystemAuthentication.info.footer" = "Serwisy na szaro nie wspierają \"wybierania katalogów\". To nie jest ograniczenie w Cryptomator."; -"maintenanceModeError.runningCloudTask" = "Operacja nie może być wykonana, ponieważ inne operacje w tle dla tego sejfu muszą zostać zakończone. Spróbuj ponownie później."; +"maintenanceModeError.runningCloudTask" = "Operacja nie może być wykonana, ponieważ inne operacje działające w tle dla tego sejfu muszą zostać zakończone. Spróbuj ponownie później."; "nameValidation.error.endsWithPeriod" = "Nazwa kończąca się kropką jest niedozwolona. Proszę wybrać inną nazwę."; "nameValidation.error.endsWithSpace" = "Nazwa kończąca się spacją jest niedozwolona. Proszę wybrać inną nazwę."; @@ -139,9 +148,9 @@ "purchase.footer.privacyPolicy" = "Polityka prywatności"; "purchase.footer.termsOfUse" = "Warunki użytkowania"; "purchase.header.feature.familySharing" = "Plan rodzinny"; -"purchase.header.feature.openSource" = "Tworzenie Open-source"; +"purchase.header.feature.openSource" = "Rozwój oprogramowania typu open-source"; "purchase.header.feature.writeAccess" = "Zapis w Twoich sejfach"; -"purchase.product.donateAndUpgrade" = "Wesprzyj i uaktualnij"; +"purchase.product.donateAndUpgrade" = "Wesprzyj i ulepsz"; "purchase.product.freeUpgrade" = "Darmowa aktualizacja"; "purchase.product.lifetimeLicense" = "Dożywotnia licencja"; "purchase.product.lifetimeLicense.duration" = "jednorazowo"; @@ -155,7 +164,7 @@ "purchase.readOnlyMode.alert.message" = "Możesz odblokować pełną wersję Cryptomator później w ustawieniach i na razie używać trybu tylko do odczytu."; "purchase.restorePurchase.button" = "Przywróć zakup"; "purchase.restorePurchase.validTrialFound.alert.title" = "Kontynuacja okresu próbnego"; -"purchase.restorePurchase.validTrialFound.alert.message" = "Teraz możesz użyć pełnej wersji Cryptomator przez ograniczony czas. Twój okres próbny kończy się w dniu %@. Po tym sejfy będą nadal dostępne w trybie tylko do odczytu."; +"purchase.restorePurchase.validTrialFound.alert.message" = "Teraz możesz używać pełnej wersji Cryptomator przez ograniczony czas. Twój okres próbny kończy się w dniu %@. Po tym sejfy będą nadal dostępne w trybie tylko do odczytu."; "purchase.restorePurchase.fullVersionFound.alert.title" = "Przywrócono pomyślnie"; "purchase.restorePurchase.fullVersionNotFound.alert.title" = "Brak pełnej wersji"; "purchase.restorePurchase.fullVersionNotFound.alert.message" = "Nie mogliśmy znaleźć wcześniej zakupionej pełnej wersji, która mogłaby zostać przywrócona. Proszę spróbować innej opcji."; @@ -164,7 +173,7 @@ "purchase.retry.button" = "Ponów"; "purchase.retry.footer" = "Nie można załadować dostępnych produktów."; "purchase.title" = "Odblokuj pełną wersję"; -"purchase.unlockedFullVersion.message" = "Możesz teraz użyć pełnej wersji Cryptomator. Szczęśliwego szyfrowania!"; +"purchase.unlockedFullVersion.message" = "Możesz teraz używać pełnej wersji Cryptomator. Szczęśliwego szyfrowania!"; "purchase.unlockedFullVersion.title" = "Dziękujemy"; "purchase.error.unknown" = "Opcja zakupu jest niedostępna w App Store z nieznanego powodu. Spróbuj ponownie później.\n\nJeśli ten błąd będzie się powtarzał, spróbuj zrestartować urządzenie lub wylogować i zalogować się swoim Apple ID w ustawieniach iOS."; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Zarządzaj subskrypcją"; "settings.rateApp" = "Oceń aplikację"; "settings.sendLogFile" = "Wyślij plik logów"; +"settings.shortcutsGuide" = "Przewodnik po skrótach"; "settings.unlockFullVersion" = "Odblokuj pełną wersję"; "snapshots.fileprovider.file1" = "/Numery.Kont"; diff --git a/SharedResources/pt-BR.lproj/Localizable.strings b/SharedResources/pt-BR.lproj/Localizable.strings index 8116a4a65..2e753d73d 100644 --- a/SharedResources/pt-BR.lproj/Localizable.strings +++ b/SharedResources/pt-BR.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Desbloquear"; "fileProvider.clearFileFromCache.title" = "Limpar Arquivos Temporários"; "fileProvider.clearFileFromCache.message" = "Isso apenas remove o arquivo do seu dispositivo local e não o exclui da nuvem."; +"fileProvider.fileImporting.error.missingPremium" = "Desbloqueie a versão completa no aplicativo Cryptomator para obter acesso de escrita aos seus cofres."; "fileProvider.uploadProgress.connecting" = "Conectando…"; "fileProvider.uploadProgress.message" = "Progresso atual: %@\n\nSe você notar que o progresso do upload está travado, pode tentar novamente o upload."; "fileProvider.uploadProgress.missing" = "Não foi possível determinar o progresso, que pode estar sendo executado em segundo plano."; "fileProvider.uploadProgress.title" = "Enviando…"; "fileProvider.uploadProgress.missingDomainError" = "Não foi possível encontrar o domínio."; +"getFolderIntent.error.missingPath" = "Nenhum caminho foi fornecido. Por favor, forneça um caminho válido para o qual a pasta deve ser retornada."; +"getFolderIntent.error.noVaultSelected" = "Nenhum cofre foi selecionado."; +"intents.saveFile.missingFile" = "O arquivo fornecido não é válido."; +"intents.saveFile.invalidFolder" = "A pasta fornecida não é válida."; +"intents.saveFile.missingTemporaryFolder" = "Falha ao criar uma pasta temporária."; +"intents.saveFile.lockedVault" = "Você precisa desbloquear o seu cofre para usar este atalho."; +"intents.saveFile.selectedVaultNotFound" = "O cofre selecionado não foi encontrado."; + "keepUnlocked.alert.title" = "Trancar cofre?"; "keepUnlocked.alert.message" = "Para que esta alteração tenha efeito é necessário que seu cofre esteja trancado."; "keepUnlocked.alert.confirm" = "Confirmar e trancar agora"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Gerenciar assinatura"; "settings.rateApp" = "Avaliar App"; "settings.sendLogFile" = "Enviar arquivo de log"; +"settings.shortcutsGuide" = "Guia de Atalhos"; "settings.unlockFullVersion" = "Desbloqueie a Versão Completa"; "snapshots.fileprovider.file1" = "/Accounting.numbers"; diff --git a/SharedResources/ru.lproj/Localizable.strings b/SharedResources/ru.lproj/Localizable.strings index 4680771d6..ca12ef5b3 100644 --- a/SharedResources/ru.lproj/Localizable.strings +++ b/SharedResources/ru.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Разблокировать"; "fileProvider.clearFileFromCache.title" = "Удалить файл из кэша"; "fileProvider.clearFileFromCache.message" = "Эта операция удаляет только локальный файл с вашего устройства, но не из облака."; +"fileProvider.fileImporting.error.missingPremium" = "Разблокируйте полную версию в приложении Cryptomator, чтобы получить возможность записи в хранилища."; "fileProvider.uploadProgress.connecting" = "Подключение…"; "fileProvider.uploadProgress.message" = "Текущий прогресс: %@\n\nЕсли вы заметите, что прогресс загрузки завис, вы можете повторить загрузку."; "fileProvider.uploadProgress.missing" = "Невозможно определить прогресс. Он может продолжаться в фоновом режиме."; "fileProvider.uploadProgress.title" = "Загрузка…"; "fileProvider.uploadProgress.missingDomainError" = "Домен не найден."; +"getFolderIntent.error.missingPath" = "Не указан путь. Укажите правильный путь, по которому должна быть возвращена папка."; +"getFolderIntent.error.noVaultSelected" = "Не выбрано хранилище."; +"intents.saveFile.missingFile" = "Выбран некорректный файл."; +"intents.saveFile.invalidFolder" = "Выбрана некорректная папка."; +"intents.saveFile.missingTemporaryFolder" = "Не удалось создать папку для временных файлов."; +"intents.saveFile.lockedVault" = "Чтобы использовать этот ярлык, нужно разблокировать хранилище."; +"intents.saveFile.selectedVaultNotFound" = "Выбранное хранилище не найдено."; + "keepUnlocked.alert.title" = "Заблокировать хранилище?"; "keepUnlocked.alert.message" = "Чтобы изменение вступило в силу, необходимо заблокировать хранилище."; "keepUnlocked.alert.confirm" = "Подтвердить и заблокировать"; diff --git a/SharedResources/si.lproj/Localizable.strings b/SharedResources/si.lproj/Localizable.strings new file mode 100644 index 000000000..25df01b83 --- /dev/null +++ b/SharedResources/si.lproj/Localizable.strings @@ -0,0 +1,8 @@ +"common.button.cancel" = "අවලංගු"; +"common.button.change" = "වෙනස් කරන්න"; +"common.button.close" = "වසන්න"; +"common.button.done" = "සම්පූර්ණයි"; +"common.button.next" = "ඊළඟ"; +"fileProvider.error.unlockButton" = "අගුළුහරින්න"; + +"unlockVault.button.unlock" = "අගුළුහරින්න"; diff --git a/SharedResources/sk.lproj/Localizable.strings b/SharedResources/sk.lproj/Localizable.strings index cc5203b2e..be8dbf344 100644 --- a/SharedResources/sk.lproj/Localizable.strings +++ b/SharedResources/sk.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Odomknúť"; "fileProvider.clearFileFromCache.title" = "Vymazať súbor z Cache"; "fileProvider.clearFileFromCache.message" = "Toto odstráni lokálny súbor iba z Vašeho zariadenia a neodstráni súbor v cloude."; +"fileProvider.fileImporting.error.missingPremium" = "Odomknite plnú verziu aplikácie Cryptomator pre získanie prístupu zápisu do Vašich trezorov."; "fileProvider.uploadProgress.connecting" = "Pripájanie…"; "fileProvider.uploadProgress.message" = "Aktuálny priebeh: %@\n\nV prípade, že pozorujete zaseknutý priebeh nahrávania, môžte zopakovať nahrávanie."; "fileProvider.uploadProgress.missing" = "Pokrok nebol spozorovaný. Môže však stále prebiehať na pozadí."; "fileProvider.uploadProgress.title" = "Nahrávanie…"; "fileProvider.uploadProgress.missingDomainError" = "Nepodarilo sa nájsť doménu."; +"getFolderIntent.error.missingPath" = "Nebola poskytnutá žiadna cesta. Prosím poskytnite platnú cestu pre adresár, ktorý by mal byť vrátený."; +"getFolderIntent.error.noVaultSelected" = "Nebol zvolený žiaden trezor."; +"intents.saveFile.missingFile" = "Poskytnutý súbor nie je platný."; +"intents.saveFile.invalidFolder" = "Poskytnutý adresár nie je platný."; +"intents.saveFile.missingTemporaryFolder" = "Nepodarilo sa vytvoriť dočasný adresár."; +"intents.saveFile.lockedVault" = "Potrebujete odomknúť Váš trezor za účelom použitia tohto odkazu."; +"intents.saveFile.selectedVaultNotFound" = "Zvolený trezor sa nenašiel."; + "keepUnlocked.alert.title" = "Zamknúť trezor?"; "keepUnlocked.alert.message" = "Táto zmena vyžaduje Váš trezor uzamknúť aby sa príkaz uplatnil."; "keepUnlocked.alert.confirm" = "Potvrdiť & uzamknúť teraz"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Spravovať predplatné"; "settings.rateApp" = "Ohodnotiť aplikáciu"; "settings.sendLogFile" = "Zaslať Log súbor"; +"settings.shortcutsGuide" = "Sprievodca skratkami"; "settings.unlockFullVersion" = "Odomknúť plnú verziu"; "snapshots.fileprovider.file1" = "/zúčtovanie.čísla"; diff --git a/SharedResources/sw-TZ.lproj/Localizable.strings b/SharedResources/sw-TZ.lproj/Localizable.strings index 2863bbc16..024d7c640 100644 --- a/SharedResources/sw-TZ.lproj/Localizable.strings +++ b/SharedResources/sw-TZ.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Fungua"; "fileProvider.clearFileFromCache.title" = "Ondoa Mafaili kutoka kwa Akiba"; "fileProvider.clearFileFromCache.message" = "Hii huondoa tu faili ya ndani kutoka kwa kifaa chako na haifuti faili kwenye wingu."; +"fileProvider.fileImporting.error.missingPremium" = "Fungua toleo kamili katika programu ya Cryptomator ili upate idhini ya kuandika kwenye kuba yako."; "fileProvider.uploadProgress.connecting" = "Kuunganisha…"; "fileProvider.uploadProgress.message" = "Maendeleo ya sasa: %@\n\nlf Ikiwa unatambua kuwa maendeleo ya kupakia yamekwama, unaweza kujaribu tena upakiaji."; "fileProvider.uploadProgress.missing" = "Maendeleo hayawezi kuamuliwa. Inaweza kuwa bado inafanya kazi kwa mandari nyuma."; "fileProvider.uploadProgress.title" = "Inapakia…"; "fileProvider.uploadProgress.missingDomainError" = "Haikuweza kupata kikoa."; +"getFolderIntent.error.missingPath" = "Hakuna njia iliyotolewa. Tafadhali toa njia halali ambayo folda inapaswa kurejeshwa."; +"getFolderIntent.error.noVaultSelected" = "Hakuna kuba iliyochaguliwa."; +"intents.saveFile.missingFile" = "Faili iliyotolewa si sahihi."; +"intents.saveFile.invalidFolder" = "Faili iliyotolewa si sahihi."; +"intents.saveFile.missingTemporaryFolder" = "Imeshindwa kuunda folda ya muda."; +"intents.saveFile.lockedVault" = "Unahitaji kufungua kuba yako ili utumie njia hii ya mkato."; +"intents.saveFile.selectedVaultNotFound" = "Kuba iliyochaguliwa haikupatikana."; + "keepUnlocked.alert.title" = "Funga Kuba?"; "keepUnlocked.alert.message" = "Mabadiliko haya yanahitaji kuba yako kufungwa ili kuanza kutumika."; "keepUnlocked.alert.confirm" = "Thibitisha & Funga Sasa"; diff --git a/SharedResources/tr.lproj/Localizable.strings b/SharedResources/tr.lproj/Localizable.strings index c715084f3..5cdcb8cc7 100644 --- a/SharedResources/tr.lproj/Localizable.strings +++ b/SharedResources/tr.lproj/Localizable.strings @@ -19,7 +19,7 @@ "common.button.download" = "İndir"; "common.button.edit" = "Düzenle"; "common.button.enable" = "Etkinleştir"; -"common.button.next" = "Sonraki"; +"common.button.next" = "İleri"; "common.button.ok" = "Tamam"; "common.button.remove" = "Kaldır"; "common.button.retry" = "Yeniden dene"; @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Kilidi Aç"; "fileProvider.clearFileFromCache.title" = "Dosyayı Önbellekten Temizle"; "fileProvider.clearFileFromCache.message" = "Bu yalnızca cihazınızdan yerel dosyayı kaldırır ve bulut içindeki dosyayı silmez."; +"fileProvider.fileImporting.error.missingPremium" = "Kasalarınıza yazma erişimi elde etmek için Cryptomator uygulamasında tam sürümün kilidini açın."; "fileProvider.uploadProgress.connecting" = "Bağlanıyor…"; "fileProvider.uploadProgress.message" = "Mevcut Süreç: %@\n\nYükleme sürecinin takıldığını fark ederseniz, yeniden yüklemeyi deneyebilirsiniz."; "fileProvider.uploadProgress.missing" = "İlerleme saptanamadı. Hala arka planda çalışıyor olabilir."; "fileProvider.uploadProgress.title" = "Yükleniyor…"; "fileProvider.uploadProgress.missingDomainError" = "Alan adı bulunamadı."; +"getFolderIntent.error.missingPath" = "Dizin sağlanmadı. Lütfen bir klasörün döndürüleceği geçerli bir dizin sağlayın."; +"getFolderIntent.error.noVaultSelected" = "Herhangi bir kasa seçili değil."; +"intents.saveFile.missingFile" = "Sağlanan dosya geçersiz."; +"intents.saveFile.invalidFolder" = "Sağlanan klasör geçersiz."; +"intents.saveFile.missingTemporaryFolder" = "Geçici klasör oluşturulamadı."; +"intents.saveFile.lockedVault" = "Bu kısayolu kullanmak için kasanızın kilidini açmanız gerekir."; +"intents.saveFile.selectedVaultNotFound" = "Seçilen kasa bulunamadı."; + "keepUnlocked.alert.title" = "Kasa kilitlensin mi?"; "keepUnlocked.alert.message" = "Yaptığınız değişikliğin geçerli olması için kasanızın kilitlenmesi gerekmektedir."; "keepUnlocked.alert.confirm" = "Onayla & Şimdi kilitle"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Aboneliği Yönet"; "settings.rateApp" = "Uygulamayı Değerlendir"; "settings.sendLogFile" = "Günlük Dosyasını Gönder"; +"settings.shortcutsGuide" = "Kısayol Kılavuzu"; "settings.unlockFullVersion" = "Tam Sürümün Kilidini Aç"; "snapshots.fileprovider.file1" = "/Sayıları.hesaplıyor"; diff --git a/SharedResources/zh-Hans.lproj/Localizable.strings b/SharedResources/zh-Hans.lproj/Localizable.strings index aab518958..bfb5c8b98 100644 --- a/SharedResources/zh-Hans.lproj/Localizable.strings +++ b/SharedResources/zh-Hans.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "解锁"; "fileProvider.clearFileFromCache.title" = "清除缓存文件"; "fileProvider.clearFileFromCache.message" = "该操作仅会删除设备中的本地文件,不会删除云存储中的文件"; +"fileProvider.fileImporting.error.missingPremium" = "在 Cryptomator 应用中解锁完整版以获取保险库写入权限"; "fileProvider.uploadProgress.connecting" = "正在连接…"; "fileProvider.uploadProgress.message" = "当前进度:%@\n\n若发现上传进度卡住,请重试上传"; "fileProvider.uploadProgress.missing" = "无法确定上传进度,可能仍在后台运行"; "fileProvider.uploadProgress.title" = "正在上传…"; "fileProvider.uploadProgress.missingDomainError" = "找不到域名"; +"getFolderIntent.error.missingPath" = "未提供路径,请提供文件夹应返回的有效路径"; +"getFolderIntent.error.noVaultSelected" = "未选择任何保险库"; +"intents.saveFile.missingFile" = "提供的文件无效"; +"intents.saveFile.invalidFolder" = "提供的文件夹无效"; +"intents.saveFile.missingTemporaryFolder" = "无法创建临时文件夹"; +"intents.saveFile.lockedVault" = "你需要解锁保险库才能使用此快捷方式"; +"intents.saveFile.selectedVaultNotFound" = "找不到选定的保险库"; + "keepUnlocked.alert.title" = "锁定保险库?"; "keepUnlocked.alert.message" = "此更改需要锁定您的保险库才能生效。"; "keepUnlocked.alert.confirm" = "确认并立即锁定"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "管理订阅"; "settings.rateApp" = "给应用评分"; "settings.sendLogFile" = "发送日志文件"; +"settings.shortcutsGuide" = "快捷方式指南"; "settings.unlockFullVersion" = "解锁完整版"; "snapshots.fileprovider.file1" = "/会计.numbers"; From 8fb8b3f88677dcd421a7d87df4d250698e6afa89 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 31 May 2022 16:01:42 +0200 Subject: [PATCH 08/15] Preparing 2.3.0 --- Cryptomator.xcodeproj/project.pbxproj | 8 ++------ fastlane/changelog.txt | 6 ++---- fastlane/metadata/de-DE/release_notes.txt | 6 ++---- fastlane/metadata/en-US/release_notes.txt | 6 ++---- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 1d0d5ff92..dbb245170 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -3101,7 +3101,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.2.5; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3163,7 +3163,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.2.5; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; @@ -3267,7 +3267,6 @@ CODE_SIGN_ENTITLEMENTS = CryptomatorIntents/CryptomatorIntents.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = AUTOMATIC; DEVELOPMENT_TEAM = YZQJQUHA3L; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = CryptomatorIntents/Info.plist; @@ -3278,7 +3277,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.5; PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.ios.intents; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development org.cryptomator.ios.intents"; @@ -3293,7 +3291,6 @@ CODE_SIGN_ENTITLEMENTS = CryptomatorIntents/CryptomatorIntents.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = AUTOMATIC; DEVELOPMENT_TEAM = YZQJQUHA3L; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = CryptomatorIntents/Info.plist; @@ -3304,7 +3301,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.5; PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.ios.intents; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.cryptomator.ios.intents"; diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 54b015833..63693234c 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1,4 +1,2 @@ -- Failed uploads are now actually shown with an error instead of being stuck in "Waiting" -- Added "Retry Upload" action (#219) -- Added "Clear from Cache" action (#149, #220) -- Added Chinese (Traditional & Hong Kong) and Swahili (Tanzania) translations \ No newline at end of file +- Added Shortcuts integration, which allows you to create automations like "auto photo upload" (#56, #114, #222) +- Improved vault format 8 compatibility (#198, cryptolib-swift#9) \ No newline at end of file diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt index fb0c2061c..8cdd7c6ad 100644 --- a/fastlane/metadata/de-DE/release_notes.txt +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -1,4 +1,2 @@ -- Fehlgeschlagene Uploads werden nun tatsächlich mit einer Fehlermeldung angezeigt, statt in „Warten“ zu hängen -- Funktion "Upload erneut versuchen" hinzugefügt (#219) -- Funktion "Aus Zwischenspeicher entfernen" hinzugefügt (#149, #220) -- Übersetzungen für Chinesisch (Langzeichen & Hongkong) und Swahili (Tansania) hinzugefügt \ No newline at end of file +- Kurzbefehle-Integration hinzugefügt, die es erlaubt, Automatisierungen wie "automatischer Foto-Upload" zu erstellen (#56, #114, #222) +- Kompatibilität mit Tresorformat 8 verbessert (#198, cryptolib-swift#9) \ No newline at end of file diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 54b015833..63693234c 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,4 +1,2 @@ -- Failed uploads are now actually shown with an error instead of being stuck in "Waiting" -- Added "Retry Upload" action (#219) -- Added "Clear from Cache" action (#149, #220) -- Added Chinese (Traditional & Hong Kong) and Swahili (Tanzania) translations \ No newline at end of file +- Added Shortcuts integration, which allows you to create automations like "auto photo upload" (#56, #114, #222) +- Improved vault format 8 compatibility (#198, cryptolib-swift#9) \ No newline at end of file From 7c6060c831ccb295bf9bf7a97135f90eba2cd105 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 1 Jun 2022 11:37:39 +0200 Subject: [PATCH 09/15] Added localization for intents --- Cryptomator.xcodeproj/project.pbxproj | 22 ++++++--- .../{ => Base.lproj}/Intents.intentdefinition | 48 ++++++++++--------- CryptomatorIntents/en.lproj/Intents.strings | 21 ++++++++ 3 files changed, 62 insertions(+), 29 deletions(-) rename CryptomatorIntents/{ => Base.lproj}/Intents.intentdefinition (93%) create mode 100644 CryptomatorIntents/en.lproj/Intents.strings diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 1d0d5ff92..11db37206 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -73,6 +73,8 @@ 4A248231266FB799002D9F59 /* FileProviderAdapterStartProvidingItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A248230266FB799002D9F59 /* FileProviderAdapterStartProvidingItemTests.swift */; }; 4A2482332670D6FB002D9F59 /* FileProviderAdapterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2482322670D6FB002D9F59 /* FileProviderAdapterManager.swift */; }; 4A2482352671110A002D9F59 /* DBManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2482342671110A002D9F59 /* DBManagerError.swift */; }; + 4A2745E228475F3500E70D5F /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4A2745E528475F3600E70D5F /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; + 4A2745E328475F3600E70D5F /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4A2745E528475F3600E70D5F /* Intents.intentdefinition */; }; 4A2C1E7B2760B09A000CD726 /* IAPViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C1E7A2760B09A000CD726 /* IAPViewModelTestCase.swift */; }; 4A2F373724B47DB800460FD3 /* UploadTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F373624B47DB800460FD3 /* UploadTaskManagerTests.swift */; }; 4A2FD04425B1C3BB008565C8 /* EmptyListMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2FD04325B1C3BB008565C8 /* EmptyListMessage.swift */; }; @@ -199,7 +201,6 @@ 4A80407D27692A0100D7D999 /* FileProviderEnumeratorSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A80407C27692A0100D7D999 /* FileProviderEnumeratorSnapshotMock.swift */; }; 4A80408027694C6600D7D999 /* VaultUnlockingServiceSourceSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A80407F27694C6600D7D999 /* VaultUnlockingServiceSourceSnapshotMock.swift */; }; 4A804082276952C300D7D999 /* FileProviderCoordinatorSnapshotMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A804081276952C300D7D999 /* FileProviderCoordinatorSnapshotMock.swift */; }; - 4A82C88A2832443D00548F87 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; 4A85ECBE283CBF4700E23024 /* FileImportingServiceSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A85ECBD283CBF4700E23024 /* FileImportingServiceSourceTests.swift */; }; 4A85ECC4283D0DED00E23024 /* GetFolderIntentHandlerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A85ECC3283D0DED00E23024 /* GetFolderIntentHandlerError.swift */; }; 4A88816427440CE300F7AA6E /* BaseUITableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A88816327440CE300F7AA6E /* BaseUITableViewController.swift */; }; @@ -273,7 +274,6 @@ 4AB8539826BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539726BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift */; }; 4AB8539C26BA8A8200555F00 /* VaultPasswordVerifying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539B26BA8A8200555F00 /* VaultPasswordVerifying.swift */; }; 4AB8539E26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8539D26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift */; }; - 4ABB4627282EBE4B00968C5E /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */; }; 4ABC08D7250D1EB600E3CEDC /* DeletionTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABC08D6250D1EB600E3CEDC /* DeletionTaskManagerTests.swift */; }; 4ABCF3522726D24800A7FBB7 /* MoveVaultViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCF3512726D24800A7FBB7 /* MoveVaultViewModelTests.swift */; }; 4AC005F127C3D80B006FFE87 /* PremiumManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC005F027C3D80B006FFE87 /* PremiumManager.swift */; }; @@ -585,6 +585,8 @@ 4A248230266FB799002D9F59 /* FileProviderAdapterStartProvidingItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAdapterStartProvidingItemTests.swift; sourceTree = ""; }; 4A2482322670D6FB002D9F59 /* FileProviderAdapterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAdapterManager.swift; sourceTree = ""; }; 4A2482342671110A002D9F59 /* DBManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerError.swift; sourceTree = ""; }; + 4A2745E428475F3600E70D5F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; + 4A2745F7284769B800E70D5F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; 4A2C1E7A2760B09A000CD726 /* IAPViewModelTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPViewModelTestCase.swift; sourceTree = ""; }; 4A2F373624B47DB800460FD3 /* UploadTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTaskManagerTests.swift; sourceTree = ""; }; 4A2FD04325B1C3BB008565C8 /* EmptyListMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyListMessage.swift; sourceTree = ""; }; @@ -790,7 +792,6 @@ 4AB8539726BA881F00555F00 /* VaultDetailUnlockVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailUnlockVaultViewModel.swift; sourceTree = ""; }; 4AB8539B26BA8A8200555F00 /* VaultPasswordVerifying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultPasswordVerifying.swift; sourceTree = ""; }; 4AB8539D26BA8B4C00555F00 /* VaultDetailUnlockCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailUnlockCoordinator.swift; sourceTree = ""; }; - 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; 4ABC08D6250D1EB600E3CEDC /* DeletionTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletionTaskManagerTests.swift; sourceTree = ""; }; 4ABCF3512726D24800A7FBB7 /* MoveVaultViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveVaultViewModelTests.swift; sourceTree = ""; }; 4AC005F027C3D80B006FFE87 /* PremiumManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumManager.swift; sourceTree = ""; }; @@ -1654,7 +1655,7 @@ isa = PBXGroup; children = ( 4A753FBB2832A371006A9C3F /* CryptomatorIntents.entitlements */, - 4ABB4626282EBE4B00968C5E /* Intents.intentdefinition */, + 4A2745E528475F3600E70D5F /* Intents.intentdefinition */, 4AD3D7DA282EBDE7008188CD /* Info.plist */, 4AA08E7E28379C6100972A15 /* GetFolderIntentHandler.swift */, 4A85ECC3283D0DED00E23024 /* GetFolderIntentHandlerError.swift */, @@ -2529,7 +2530,7 @@ buildActionMask = 2147483647; files = ( 4A33092B282EBF9900876A3E /* SaveFileIntentHandler.swift in Sources */, - 4ABB4627282EBE4B00968C5E /* Intents.intentdefinition in Sources */, + 4A2745E328475F3600E70D5F /* Intents.intentdefinition in Sources */, 4A85ECC4283D0DED00E23024 /* GetFolderIntentHandlerError.swift in Sources */, 4AD3D7D9282EBDE7008188CD /* IntentHandler.swift in Sources */, 4AA08E7F28379C6100972A15 /* GetFolderIntentHandler.swift in Sources */, @@ -2696,7 +2697,7 @@ 4A644B4B267B4C08008CBB9A /* CreateNewVaultChooseFolderViewController.swift in Sources */, 4A447E1B25BF0DE300D9520D /* ChooseFolderViewController.swift in Sources */, 4A88816427440CE300F7AA6E /* BaseUITableViewController.swift in Sources */, - 4A82C88A2832443D00548F87 /* Intents.intentdefinition in Sources */, + 4A2745E228475F3500E70D5F /* Intents.intentdefinition in Sources */, 4AF91CE225A7234500ACF01E /* DatabaseManager.swift in Sources */, 4A3C5DDA272BF52600EB7C7A /* TextFieldCellViewModel.swift in Sources */, 4AFCE51F25B89CD80069C4FC /* CloudProviderType+Localization.swift in Sources */, @@ -2896,6 +2897,15 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 4A2745E528475F3600E70D5F /* Intents.intentdefinition */ = { + isa = PBXVariantGroup; + children = ( + 4A2745E428475F3600E70D5F /* Base */, + 4A2745F7284769B800E70D5F /* en */, + ); + name = Intents.intentdefinition; + sourceTree = ""; + }; 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/CryptomatorIntents/Intents.intentdefinition b/CryptomatorIntents/Base.lproj/Intents.intentdefinition similarity index 93% rename from CryptomatorIntents/Intents.intentdefinition rename to CryptomatorIntents/Base.lproj/Intents.intentdefinition index b50b293b2..1bd6f5b7b 100644 --- a/CryptomatorIntents/Intents.intentdefinition +++ b/CryptomatorIntents/Base.lproj/Intents.intentdefinition @@ -24,9 +24,9 @@ INIntentDefaultImageName bot INIntentDescription - Saves a file to a vault + Saves a file to a vault. INIntentDescriptionID - UN1SvF + saveFileIntent.description INIntentIneligibleForSuggestions INIntentInput @@ -44,7 +44,7 @@ INIntentParameterCombinationTitle Save ${file} to ${folder} INIntentParameterCombinationTitleID - PkFbjn + saveFileIntent.text INIntentParameterCombinationUpdatesLinked @@ -59,7 +59,7 @@ INIntentParameterDisplayName File INIntentParameterDisplayNameID - AJzrnx + saveFileIntent.file INIntentParameterDisplayPriority 1 INIntentParameterMetadata @@ -112,7 +112,7 @@ INIntentParameterDisplayName Folder INIntentParameterDisplayNameID - 2o44Pd + common.folder INIntentParameterDisplayPriority 2 INIntentParameterName @@ -149,7 +149,7 @@ INIntentParameterDisplayName Ignore existing file with same name INIntentParameterDisplayNameID - ft89SM + saveFileIntent.parameter.ignoreExisting INIntentParameterDisplayPriority 3 INIntentParameterMetadata @@ -159,11 +159,11 @@ INIntentParameterMetadataFalseDisplayName false INIntentParameterMetadataFalseDisplayNameID - WrUDZT + common.false INIntentParameterMetadataTrueDisplayName true INIntentParameterMetadataTrueDisplayNameID - 3g4nj9 + common.true INIntentParameterName ignoreExisting @@ -202,7 +202,7 @@ INIntentResponseCodeConciseFormatString ${failureReason} INIntentResponseCodeConciseFormatStringID - NSH4gZ + common.response.failure INIntentResponseCodeName failure @@ -215,7 +215,7 @@ INIntentResponseParameterDisplayName Failure Reason INIntentResponseParameterDisplayNameID - CjPrfS + common.failureReason INIntentResponseParameterDisplayPriority 1 INIntentResponseParameterName @@ -230,7 +230,7 @@ INIntentTitle Save File INIntentTitleID - dlePJ1 + saveFileIntent.title INIntentType Custom INIntentVerb @@ -241,8 +241,10 @@ generic INIntentConfigurable + INIntentDescription + Returns a folder object for the given path in the given vault. INIntentDescriptionID - dh0Nnc + getFolderIntent.description INIntentIneligibleForSuggestions INIntentLastParameterTag @@ -256,7 +258,7 @@ INIntentParameterCombinationTitle Get folder located at ${path} in ${vault} INIntentParameterCombinationTitleID - getFolderIntent.test + getFolderIntent.text INIntentParameterCombinationUpdatesLinked @@ -271,7 +273,7 @@ INIntentParameterDisplayName Path INIntentParameterDisplayNameID - 0wBj7w + getFolderIntent.path INIntentParameterDisplayPriority 1 INIntentParameterMetadata @@ -309,7 +311,7 @@ INIntentParameterDisplayName Vault INIntentParameterDisplayNameID - W0waaV + common.vault INIntentParameterDisplayPriority 2 INIntentParameterName @@ -355,7 +357,7 @@ INIntentResponseCodeConciseFormatString ${failureReason} INIntentResponseCodeConciseFormatStringID - KDmUbi + common.response.failure INIntentResponseCodeName failure @@ -370,7 +372,7 @@ INIntentResponseParameterDisplayName Folder INIntentResponseParameterDisplayNameID - 7aJMCw + common.folder INIntentResponseParameterDisplayPriority 1 INIntentResponseParameterName @@ -388,7 +390,7 @@ INIntentResponseParameterDisplayName Failure Reason INIntentResponseParameterDisplayNameID - EdwH3n + common.failureReason INIntentResponseParameterDisplayPriority 2 INIntentResponseParameterName @@ -403,7 +405,7 @@ INIntentTitle Get Folder INIntentTitleID - Fdd6ta + getFolderIntent.title INIntentType Custom INIntentVerb @@ -416,7 +418,7 @@ INTypeDisplayName Vault INTypeDisplayNameID - lXc2vD + common.vault INTypeLastPropertyTag 99 INTypeName @@ -477,9 +479,9 @@ INTypeDisplayName - VaultFolder + Vault Folder INTypeDisplayNameID - 9EErZb + vaultFolder.displayName INTypeLastPropertyTag 100 INTypeName @@ -540,7 +542,7 @@ INTypePropertyDisplayName Vault Identifier INTypePropertyDisplayNameID - BJqJ7P + vaultFolder.vaultIdentifier INTypePropertyDisplayPriority 5 INTypePropertyName diff --git a/CryptomatorIntents/en.lproj/Intents.strings b/CryptomatorIntents/en.lproj/Intents.strings new file mode 100644 index 000000000..70747f219 --- /dev/null +++ b/CryptomatorIntents/en.lproj/Intents.strings @@ -0,0 +1,21 @@ +"common.failureReason" = "Failure Reason"; +"common.false" = "false"; +"common.folder" = "Folder"; +"common.response.failure" = "${failureReason}"; +"common.true" = "true"; +"common.vault" = "Vault"; + +"getFolderIntent.description" = "Returns a folder object for the given path in the given vault."; +"getFolderIntent.path" = "Path"; +"getFolderIntent.text" = "Get folder located at ${path} in ${vault}"; +"getFolderIntent.title" = "Get Folder"; + +"saveFileIntent.description" = "Saves a file to a vault."; +"saveFileIntent.file" = "File"; +"saveFileIntent.parameter.ignoreExisting" = "Ignore existing file with same name"; +"saveFileIntent.text" = "Save ${file} to ${folder}"; +"saveFileIntent.title" = "Save File"; + +"vaultFolder.displayName" = "Vault Folder"; +"vaultFolder.vaultIdentifier" = "Vault Identifier"; + From 4d82fa25752b6952a995ae3fce73dcf74b182ad5 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 1 Jun 2022 12:03:48 +0200 Subject: [PATCH 10/15] Added Intents.strings to Crowdin --- .crowdin.yml | 5 +++++ CryptomatorIntents/en.lproj/Intents.strings | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.crowdin.yml b/.crowdin.yml index 996007c05..b37365df2 100644 --- a/.crowdin.yml +++ b/.crowdin.yml @@ -2,4 +2,9 @@ commit_message: '[ci skip]' files: - source: /SharedResources/en.lproj/Localizable.strings translation: /SharedResources/%osx_code%/%original_file_name% + skip_untranslated_strings: false + skip_untranslated_files: true + - source: /CryptomatorIntents/en.lproj/Intents.strings + translation: /CryptomatorIntents/%osx_code%/%original_file_name% + skip_untranslated_strings: false skip_untranslated_files: true diff --git a/CryptomatorIntents/en.lproj/Intents.strings b/CryptomatorIntents/en.lproj/Intents.strings index 70747f219..595b78814 100644 --- a/CryptomatorIntents/en.lproj/Intents.strings +++ b/CryptomatorIntents/en.lproj/Intents.strings @@ -18,4 +18,3 @@ "vaultFolder.displayName" = "Vault Folder"; "vaultFolder.vaultIdentifier" = "Vault Identifier"; - From d795f67db8409f6469fba9d0073036b214e5343b Mon Sep 17 00:00:00 2001 From: Cryptobot Date: Wed, 1 Jun 2022 13:54:46 +0200 Subject: [PATCH 11/15] New Crowdin updates (#226) [ci skip] --- CryptomatorIntents/ar.lproj/Intents.strings | 1 + CryptomatorIntents/bn.lproj/Intents.strings | 1 + CryptomatorIntents/bs.lproj/Intents.strings | 1 + CryptomatorIntents/ca.lproj/Intents.strings | 1 + CryptomatorIntents/cs.lproj/Intents.strings | 1 + CryptomatorIntents/de.lproj/Intents.strings | 20 +++++++++++++++++++ CryptomatorIntents/el.lproj/Intents.strings | 1 + CryptomatorIntents/es.lproj/Intents.strings | 1 + CryptomatorIntents/fa.lproj/Intents.strings | 0 CryptomatorIntents/fil.lproj/Intents.strings | 1 + CryptomatorIntents/fr.lproj/Intents.strings | 1 + CryptomatorIntents/gl.lproj/Intents.strings | 0 CryptomatorIntents/he.lproj/Intents.strings | 1 + CryptomatorIntents/hi.lproj/Intents.strings | 1 + CryptomatorIntents/hr.lproj/Intents.strings | 1 + CryptomatorIntents/hu.lproj/Intents.strings | 1 + CryptomatorIntents/id.lproj/Intents.strings | 1 + CryptomatorIntents/it.lproj/Intents.strings | 14 +++++++++++++ CryptomatorIntents/ja.lproj/Intents.strings | 1 + CryptomatorIntents/ko.lproj/Intents.strings | 1 + CryptomatorIntents/lv.lproj/Intents.strings | 0 CryptomatorIntents/mk.lproj/Intents.strings | 0 CryptomatorIntents/nb.lproj/Intents.strings | 1 + CryptomatorIntents/nl.lproj/Intents.strings | 1 + .../nn-NO.lproj/Intents.strings | 0 CryptomatorIntents/pa.lproj/Intents.strings | 1 + CryptomatorIntents/pl.lproj/Intents.strings | 7 +++++++ .../pt-BR.lproj/Intents.strings | 1 + CryptomatorIntents/pt.lproj/Intents.strings | 1 + CryptomatorIntents/ro.lproj/Intents.strings | 1 + CryptomatorIntents/ru.lproj/Intents.strings | 6 ++++++ CryptomatorIntents/si.lproj/Intents.strings | 0 CryptomatorIntents/sk.lproj/Intents.strings | 1 + .../sr-Latn.lproj/Intents.strings | 1 + CryptomatorIntents/sr.lproj/Intents.strings | 1 + CryptomatorIntents/sv.lproj/Intents.strings | 1 + .../sw-TZ.lproj/Intents.strings | 1 + CryptomatorIntents/ta.lproj/Intents.strings | 0 CryptomatorIntents/te.lproj/Intents.strings | 0 CryptomatorIntents/th.lproj/Intents.strings | 1 + CryptomatorIntents/tr.lproj/Intents.strings | 1 + CryptomatorIntents/uk.lproj/Intents.strings | 1 + .../zh-HK.lproj/Intents.strings | 1 + .../zh-Hans.lproj/Intents.strings | 1 + .../zh-Hant.lproj/Intents.strings | 1 + SharedResources/de.lproj/Localizable.strings | 8 ++++---- 46 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 CryptomatorIntents/ar.lproj/Intents.strings create mode 100644 CryptomatorIntents/bn.lproj/Intents.strings create mode 100644 CryptomatorIntents/bs.lproj/Intents.strings create mode 100644 CryptomatorIntents/ca.lproj/Intents.strings create mode 100644 CryptomatorIntents/cs.lproj/Intents.strings create mode 100644 CryptomatorIntents/de.lproj/Intents.strings create mode 100644 CryptomatorIntents/el.lproj/Intents.strings create mode 100644 CryptomatorIntents/es.lproj/Intents.strings create mode 100644 CryptomatorIntents/fa.lproj/Intents.strings create mode 100644 CryptomatorIntents/fil.lproj/Intents.strings create mode 100644 CryptomatorIntents/fr.lproj/Intents.strings create mode 100644 CryptomatorIntents/gl.lproj/Intents.strings create mode 100644 CryptomatorIntents/he.lproj/Intents.strings create mode 100644 CryptomatorIntents/hi.lproj/Intents.strings create mode 100644 CryptomatorIntents/hr.lproj/Intents.strings create mode 100644 CryptomatorIntents/hu.lproj/Intents.strings create mode 100644 CryptomatorIntents/id.lproj/Intents.strings create mode 100644 CryptomatorIntents/it.lproj/Intents.strings create mode 100644 CryptomatorIntents/ja.lproj/Intents.strings create mode 100644 CryptomatorIntents/ko.lproj/Intents.strings create mode 100644 CryptomatorIntents/lv.lproj/Intents.strings create mode 100644 CryptomatorIntents/mk.lproj/Intents.strings create mode 100644 CryptomatorIntents/nb.lproj/Intents.strings create mode 100644 CryptomatorIntents/nl.lproj/Intents.strings create mode 100644 CryptomatorIntents/nn-NO.lproj/Intents.strings create mode 100644 CryptomatorIntents/pa.lproj/Intents.strings create mode 100644 CryptomatorIntents/pl.lproj/Intents.strings create mode 100644 CryptomatorIntents/pt-BR.lproj/Intents.strings create mode 100644 CryptomatorIntents/pt.lproj/Intents.strings create mode 100644 CryptomatorIntents/ro.lproj/Intents.strings create mode 100644 CryptomatorIntents/ru.lproj/Intents.strings create mode 100644 CryptomatorIntents/si.lproj/Intents.strings create mode 100644 CryptomatorIntents/sk.lproj/Intents.strings create mode 100644 CryptomatorIntents/sr-Latn.lproj/Intents.strings create mode 100644 CryptomatorIntents/sr.lproj/Intents.strings create mode 100644 CryptomatorIntents/sv.lproj/Intents.strings create mode 100644 CryptomatorIntents/sw-TZ.lproj/Intents.strings create mode 100644 CryptomatorIntents/ta.lproj/Intents.strings create mode 100644 CryptomatorIntents/te.lproj/Intents.strings create mode 100644 CryptomatorIntents/th.lproj/Intents.strings create mode 100644 CryptomatorIntents/tr.lproj/Intents.strings create mode 100644 CryptomatorIntents/uk.lproj/Intents.strings create mode 100644 CryptomatorIntents/zh-HK.lproj/Intents.strings create mode 100644 CryptomatorIntents/zh-Hans.lproj/Intents.strings create mode 100644 CryptomatorIntents/zh-Hant.lproj/Intents.strings diff --git a/CryptomatorIntents/ar.lproj/Intents.strings b/CryptomatorIntents/ar.lproj/Intents.strings new file mode 100644 index 000000000..844de668e --- /dev/null +++ b/CryptomatorIntents/ar.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "مخزن"; diff --git a/CryptomatorIntents/bn.lproj/Intents.strings b/CryptomatorIntents/bn.lproj/Intents.strings new file mode 100644 index 000000000..de4f23be6 --- /dev/null +++ b/CryptomatorIntents/bn.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "ভোল্ট"; diff --git a/CryptomatorIntents/bs.lproj/Intents.strings b/CryptomatorIntents/bs.lproj/Intents.strings new file mode 100644 index 000000000..a5a361fff --- /dev/null +++ b/CryptomatorIntents/bs.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Sef"; diff --git a/CryptomatorIntents/ca.lproj/Intents.strings b/CryptomatorIntents/ca.lproj/Intents.strings new file mode 100644 index 000000000..53510b6aa --- /dev/null +++ b/CryptomatorIntents/ca.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Caixa forta"; diff --git a/CryptomatorIntents/cs.lproj/Intents.strings b/CryptomatorIntents/cs.lproj/Intents.strings new file mode 100644 index 000000000..d27759f3f --- /dev/null +++ b/CryptomatorIntents/cs.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Trezor"; diff --git a/CryptomatorIntents/de.lproj/Intents.strings b/CryptomatorIntents/de.lproj/Intents.strings new file mode 100644 index 000000000..0affe6197 --- /dev/null +++ b/CryptomatorIntents/de.lproj/Intents.strings @@ -0,0 +1,20 @@ +"common.failureReason" = "Fehlerursache"; +"common.false" = "falsch"; +"common.folder" = "Ordner"; +"common.response.failure" = "${failureReason}"; +"common.true" = "wahr"; +"common.vault" = "Tresor"; + +"getFolderIntent.description" = "Gibt ein Ordnerobjekt für den angegebenen Pfad im angegebenen Tresor zurück."; +"getFolderIntent.path" = "Pfad"; +"getFolderIntent.text" = "Ordner unter ${path} in ${vault} abrufen"; +"getFolderIntent.title" = "Ordner abrufen"; + +"saveFileIntent.description" = "Speichert eine Datei in einem Tresor."; +"saveFileIntent.file" = "Datei"; +"saveFileIntent.parameter.ignoreExisting" = "Vorhandene Datei mit gleichem Namen ignorieren"; +"saveFileIntent.text" = "${file} in ${folder} speichern"; +"saveFileIntent.title" = "Datei speichern"; + +"vaultFolder.displayName" = "Tresor-Ordner"; +"vaultFolder.vaultIdentifier" = "Tresor-Bezeichnung"; diff --git a/CryptomatorIntents/el.lproj/Intents.strings b/CryptomatorIntents/el.lproj/Intents.strings new file mode 100644 index 000000000..917e3f9ef --- /dev/null +++ b/CryptomatorIntents/el.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Κρύπτη"; diff --git a/CryptomatorIntents/es.lproj/Intents.strings b/CryptomatorIntents/es.lproj/Intents.strings new file mode 100644 index 000000000..9e1c9a038 --- /dev/null +++ b/CryptomatorIntents/es.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Bóveda"; diff --git a/CryptomatorIntents/fa.lproj/Intents.strings b/CryptomatorIntents/fa.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/fil.lproj/Intents.strings b/CryptomatorIntents/fil.lproj/Intents.strings new file mode 100644 index 000000000..c369e1822 --- /dev/null +++ b/CryptomatorIntents/fil.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Vault"; diff --git a/CryptomatorIntents/fr.lproj/Intents.strings b/CryptomatorIntents/fr.lproj/Intents.strings new file mode 100644 index 000000000..212effdc0 --- /dev/null +++ b/CryptomatorIntents/fr.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Coffre"; diff --git a/CryptomatorIntents/gl.lproj/Intents.strings b/CryptomatorIntents/gl.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/he.lproj/Intents.strings b/CryptomatorIntents/he.lproj/Intents.strings new file mode 100644 index 000000000..9b44536c6 --- /dev/null +++ b/CryptomatorIntents/he.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "כספת"; diff --git a/CryptomatorIntents/hi.lproj/Intents.strings b/CryptomatorIntents/hi.lproj/Intents.strings new file mode 100644 index 000000000..e2efc38b7 --- /dev/null +++ b/CryptomatorIntents/hi.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "गुप्त तिजोरी"; diff --git a/CryptomatorIntents/hr.lproj/Intents.strings b/CryptomatorIntents/hr.lproj/Intents.strings new file mode 100644 index 000000000..d27759f3f --- /dev/null +++ b/CryptomatorIntents/hr.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Trezor"; diff --git a/CryptomatorIntents/hu.lproj/Intents.strings b/CryptomatorIntents/hu.lproj/Intents.strings new file mode 100644 index 000000000..5398e7ff9 --- /dev/null +++ b/CryptomatorIntents/hu.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Széf"; diff --git a/CryptomatorIntents/id.lproj/Intents.strings b/CryptomatorIntents/id.lproj/Intents.strings new file mode 100644 index 000000000..c369e1822 --- /dev/null +++ b/CryptomatorIntents/id.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Vault"; diff --git a/CryptomatorIntents/it.lproj/Intents.strings b/CryptomatorIntents/it.lproj/Intents.strings new file mode 100644 index 000000000..2c8fb6228 --- /dev/null +++ b/CryptomatorIntents/it.lproj/Intents.strings @@ -0,0 +1,14 @@ +"common.failureReason" = "Motivo del fallimento"; +"common.false" = "falso"; +"common.folder" = "Cartella"; +"common.response.failure" = "${failureReason}"; +"common.true" = "vero"; +"common.vault" = "Cassaforte"; +"getFolderIntent.path" = "Percorso"; + +"saveFileIntent.description" = "Salva un file in una cassaforte."; +"saveFileIntent.file" = "File"; +"saveFileIntent.parameter.ignoreExisting" = "Ignora i file esistenti con lo stesso nome"; +"saveFileIntent.text" = "Salva ${file} su ${folder}"; + +"vaultFolder.displayName" = "Cartella Cassaforte"; diff --git a/CryptomatorIntents/ja.lproj/Intents.strings b/CryptomatorIntents/ja.lproj/Intents.strings new file mode 100644 index 000000000..4bbd2c47f --- /dev/null +++ b/CryptomatorIntents/ja.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "金庫"; diff --git a/CryptomatorIntents/ko.lproj/Intents.strings b/CryptomatorIntents/ko.lproj/Intents.strings new file mode 100644 index 000000000..c369e1822 --- /dev/null +++ b/CryptomatorIntents/ko.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Vault"; diff --git a/CryptomatorIntents/lv.lproj/Intents.strings b/CryptomatorIntents/lv.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/mk.lproj/Intents.strings b/CryptomatorIntents/mk.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/nb.lproj/Intents.strings b/CryptomatorIntents/nb.lproj/Intents.strings new file mode 100644 index 000000000..cad778401 --- /dev/null +++ b/CryptomatorIntents/nb.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Hvelv"; diff --git a/CryptomatorIntents/nl.lproj/Intents.strings b/CryptomatorIntents/nl.lproj/Intents.strings new file mode 100644 index 000000000..ea06747fb --- /dev/null +++ b/CryptomatorIntents/nl.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Kluis"; diff --git a/CryptomatorIntents/nn-NO.lproj/Intents.strings b/CryptomatorIntents/nn-NO.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/pa.lproj/Intents.strings b/CryptomatorIntents/pa.lproj/Intents.strings new file mode 100644 index 000000000..ac591e060 --- /dev/null +++ b/CryptomatorIntents/pa.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "ਵਾਲਟ"; diff --git a/CryptomatorIntents/pl.lproj/Intents.strings b/CryptomatorIntents/pl.lproj/Intents.strings new file mode 100644 index 000000000..0c4980e06 --- /dev/null +++ b/CryptomatorIntents/pl.lproj/Intents.strings @@ -0,0 +1,7 @@ +"common.failureReason" = "Powód niepowodzenia"; +"common.folder" = "Folder"; +"common.vault" = "Sejf"; +"getFolderIntent.path" = "Ścieżka"; +"getFolderIntent.text" = "Pobierz folder znajdujący się w ${path} w ${vault}"; +"getFolderIntent.title" = "Pobierz folder"; +"saveFileIntent.file" = "Plik"; diff --git a/CryptomatorIntents/pt-BR.lproj/Intents.strings b/CryptomatorIntents/pt-BR.lproj/Intents.strings new file mode 100644 index 000000000..e0c7bc1f6 --- /dev/null +++ b/CryptomatorIntents/pt-BR.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Cofre"; diff --git a/CryptomatorIntents/pt.lproj/Intents.strings b/CryptomatorIntents/pt.lproj/Intents.strings new file mode 100644 index 000000000..e0c7bc1f6 --- /dev/null +++ b/CryptomatorIntents/pt.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Cofre"; diff --git a/CryptomatorIntents/ro.lproj/Intents.strings b/CryptomatorIntents/ro.lproj/Intents.strings new file mode 100644 index 000000000..701e1bb32 --- /dev/null +++ b/CryptomatorIntents/ro.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Seif"; diff --git a/CryptomatorIntents/ru.lproj/Intents.strings b/CryptomatorIntents/ru.lproj/Intents.strings new file mode 100644 index 000000000..72cab1c0e --- /dev/null +++ b/CryptomatorIntents/ru.lproj/Intents.strings @@ -0,0 +1,6 @@ +"common.failureReason" = "Причина ошибки"; +"common.false" = "ложь"; +"common.folder" = "Папка"; +"common.response.failure" = "${failureReason}"; +"common.true" = "истина"; +"common.vault" = "Хранилище"; diff --git a/CryptomatorIntents/si.lproj/Intents.strings b/CryptomatorIntents/si.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/sk.lproj/Intents.strings b/CryptomatorIntents/sk.lproj/Intents.strings new file mode 100644 index 000000000..d27759f3f --- /dev/null +++ b/CryptomatorIntents/sk.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Trezor"; diff --git a/CryptomatorIntents/sr-Latn.lproj/Intents.strings b/CryptomatorIntents/sr-Latn.lproj/Intents.strings new file mode 100644 index 000000000..a5a361fff --- /dev/null +++ b/CryptomatorIntents/sr-Latn.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Sef"; diff --git a/CryptomatorIntents/sr.lproj/Intents.strings b/CryptomatorIntents/sr.lproj/Intents.strings new file mode 100644 index 000000000..74c0da3a1 --- /dev/null +++ b/CryptomatorIntents/sr.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Сеф"; diff --git a/CryptomatorIntents/sv.lproj/Intents.strings b/CryptomatorIntents/sv.lproj/Intents.strings new file mode 100644 index 000000000..52f11478c --- /dev/null +++ b/CryptomatorIntents/sv.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Valv"; diff --git a/CryptomatorIntents/sw-TZ.lproj/Intents.strings b/CryptomatorIntents/sw-TZ.lproj/Intents.strings new file mode 100644 index 000000000..f76fc6055 --- /dev/null +++ b/CryptomatorIntents/sw-TZ.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Kuba"; diff --git a/CryptomatorIntents/ta.lproj/Intents.strings b/CryptomatorIntents/ta.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/te.lproj/Intents.strings b/CryptomatorIntents/te.lproj/Intents.strings new file mode 100644 index 000000000..e69de29bb diff --git a/CryptomatorIntents/th.lproj/Intents.strings b/CryptomatorIntents/th.lproj/Intents.strings new file mode 100644 index 000000000..c369e1822 --- /dev/null +++ b/CryptomatorIntents/th.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Vault"; diff --git a/CryptomatorIntents/tr.lproj/Intents.strings b/CryptomatorIntents/tr.lproj/Intents.strings new file mode 100644 index 000000000..c3ca13058 --- /dev/null +++ b/CryptomatorIntents/tr.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Kasa"; diff --git a/CryptomatorIntents/uk.lproj/Intents.strings b/CryptomatorIntents/uk.lproj/Intents.strings new file mode 100644 index 000000000..3e7a71b6e --- /dev/null +++ b/CryptomatorIntents/uk.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "Сховище"; diff --git a/CryptomatorIntents/zh-HK.lproj/Intents.strings b/CryptomatorIntents/zh-HK.lproj/Intents.strings new file mode 100644 index 000000000..50d3b3bb8 --- /dev/null +++ b/CryptomatorIntents/zh-HK.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "加密庫"; diff --git a/CryptomatorIntents/zh-Hans.lproj/Intents.strings b/CryptomatorIntents/zh-Hans.lproj/Intents.strings new file mode 100644 index 000000000..4a1575369 --- /dev/null +++ b/CryptomatorIntents/zh-Hans.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "保险库"; diff --git a/CryptomatorIntents/zh-Hant.lproj/Intents.strings b/CryptomatorIntents/zh-Hant.lproj/Intents.strings new file mode 100644 index 000000000..788a63312 --- /dev/null +++ b/CryptomatorIntents/zh-Hant.lproj/Intents.strings @@ -0,0 +1 @@ +"common.vault" = "加密檔案庫"; diff --git a/SharedResources/de.lproj/Localizable.strings b/SharedResources/de.lproj/Localizable.strings index 37c69cea2..14fcc2b56 100644 --- a/SharedResources/de.lproj/Localizable.strings +++ b/SharedResources/de.lproj/Localizable.strings @@ -100,19 +100,19 @@ "fileProvider.error.unlockButton" = "Entsperren"; "fileProvider.clearFileFromCache.title" = "Datei aus Zwischenspeicher entfernen"; "fileProvider.clearFileFromCache.message" = "Dies entfernt nur die lokale Datei von Ihrem Gerät und löscht nicht die Datei in der Cloud."; -"fileProvider.fileImporting.error.missingPremium" = "Schalten Sie die Vollversion in der Cryptomator-App frei, um Schreibzugriff auf Ihre Tresore zu erhalten."; +"fileProvider.fileImporting.error.missingPremium" = "Schalte die Vollversion in der Cryptomator-App frei, um Schreibzugriff auf deine Tresore zu erhalten."; "fileProvider.uploadProgress.connecting" = "Verbindung wird hergestellt …"; "fileProvider.uploadProgress.message" = "Aktueller Fortschritt: %@\n\nFalls du feststellst, dass der Upload feststeckt, kannst du den Upload erneut versuchen."; "fileProvider.uploadProgress.missing" = "Der Fortschritt konnte nicht ermittelt werden. Möglicherweise läuft der Upload noch im Hintergrund."; "fileProvider.uploadProgress.title" = "Wird hochgeladen …"; "fileProvider.uploadProgress.missingDomainError" = "Domain konnte nicht gefunden werden."; -"getFolderIntent.error.missingPath" = "Es wurde kein Pfad angegeben. Bitte geben Sie einen gültigen Pfad an, für den ein Ordner angegeben werden soll."; +"getFolderIntent.error.missingPath" = "Es wurde kein Pfad angegeben. Bitte gib einen gültigen Pfad zu einem Ordner an."; "getFolderIntent.error.noVaultSelected" = "Kein Tresor ausgewählt."; "intents.saveFile.missingFile" = "Die angegebene Datei ist ungültig."; "intents.saveFile.invalidFolder" = "Der angegebene Ordner ist ungültig."; "intents.saveFile.missingTemporaryFolder" = "Erstellung eines temporären Ordners fehlgeschlagen."; -"intents.saveFile.lockedVault" = "Sie müssen Ihren Tresor entsperren, um diese Verknüpfung nutzen zu können."; +"intents.saveFile.lockedVault" = "Du musst deinen Tresor entsperren, um diesen Kurzbefehl nutzen zu können."; "intents.saveFile.selectedVaultNotFound" = "Ausgewählter Tresor konnte nicht gefunden werden."; "keepUnlocked.alert.title" = "Tresor sperren?"; @@ -189,7 +189,7 @@ "settings.manageSubscriptions" = "Abonnement verwalten"; "settings.rateApp" = "App bewerten"; "settings.sendLogFile" = "Protokolldatei senden"; -"settings.shortcutsGuide" = "Kurzbefehle"; +"settings.shortcutsGuide" = "Leitfaden für Kurzbefehle"; "settings.unlockFullVersion" = "Vollversion freischalten"; "snapshots.fileprovider.file1" = "/Buchhaltung.numbers"; From 7e1f70a58957a96dc2bc75dea5a595157c60d81a Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 1 Jun 2022 14:09:23 +0200 Subject: [PATCH 12/15] Enabled translations for CryptomatorIntents --- Cryptomator.xcodeproj/project.pbxproj | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 11db37206..5ab04bbec 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -928,6 +928,34 @@ 74267A1A26A5799A004C61BC /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; 74267A1C26A5799F004C61BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; 74267A1D26A579A4004C61BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 74275ACC28478DFA0058AD25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Intents.strings; sourceTree = ""; }; + 74275ACD28478DFC0058AD25 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; }; + 74275ACE28478DFD0058AD25 /* bn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bn; path = bn.lproj/Intents.strings; sourceTree = ""; }; + 74275ACF28478DFE0058AD25 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intents.strings; sourceTree = ""; }; + 74275AD028478DFF0058AD25 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Intents.strings"; sourceTree = ""; }; + 74275AD128478DFF0058AD25 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Intents.strings"; sourceTree = ""; }; + 74275AD228478E000058AD25 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Intents.strings"; sourceTree = ""; }; + 74275AD328478E010058AD25 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Intents.strings; sourceTree = ""; }; + 74275AD428478E020058AD25 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Intents.strings; sourceTree = ""; }; + 74275AD528478E030058AD25 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Intents.strings; sourceTree = ""; }; + 74275AD628478E040058AD25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = ""; }; + 74275AD728478E050058AD25 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Intents.strings; sourceTree = ""; }; + 74275AD828478E060058AD25 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Intents.strings; sourceTree = ""; }; + 74275AD928478E070058AD25 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Intents.strings; sourceTree = ""; }; + 74275ADA28478E080058AD25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; + 74275ADB28478E090058AD25 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Intents.strings; sourceTree = ""; }; + 74275ADC28478E0A0058AD25 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Intents.strings; sourceTree = ""; }; + 74275ADD28478E0B0058AD25 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = ""; }; + 74275ADE28478E0C0058AD25 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = ""; }; + 74275ADF28478E0D0058AD25 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Intents.strings; sourceTree = ""; }; + 74275AE028478E0E0058AD25 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Intents.strings"; sourceTree = ""; }; + 74275AE128478E0F0058AD25 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Intents.strings; sourceTree = ""; }; + 74275AE228478E100058AD25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Intents.strings; sourceTree = ""; }; + 74275AE328478E120058AD25 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Intents.strings; sourceTree = ""; }; + 74275AE428478E130058AD25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = ""; }; + 74275AE528478E140058AD25 /* sw-TZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sw-TZ"; path = "sw-TZ.lproj/Intents.strings"; sourceTree = ""; }; + 74275AE628478E140058AD25 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = ""; }; + 74275AE728478E160058AD25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Intents.strings; sourceTree = ""; }; 74397A842832A05E00CB9410 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 74397A852832A09B00CB9410 /* sw-TZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sw-TZ"; path = "sw-TZ.lproj/Localizable.strings"; sourceTree = ""; }; 7460FFED26FB6C100018BCC4 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; @@ -2902,6 +2930,34 @@ children = ( 4A2745E428475F3600E70D5F /* Base */, 4A2745F7284769B800E70D5F /* en */, + 74275ACC28478DFA0058AD25 /* de */, + 74275ACD28478DFC0058AD25 /* ar */, + 74275ACE28478DFD0058AD25 /* bn */, + 74275ACF28478DFE0058AD25 /* ca */, + 74275AD028478DFF0058AD25 /* zh-HK */, + 74275AD128478DFF0058AD25 /* zh-Hans */, + 74275AD228478E000058AD25 /* zh-Hant */, + 74275AD328478E010058AD25 /* hr */, + 74275AD428478E020058AD25 /* cs */, + 74275AD528478E030058AD25 /* nl */, + 74275AD628478E040058AD25 /* fr */, + 74275AD728478E050058AD25 /* el */, + 74275AD828478E060058AD25 /* hi */, + 74275AD928478E070058AD25 /* id */, + 74275ADA28478E080058AD25 /* it */, + 74275ADB28478E090058AD25 /* ja */, + 74275ADC28478E0A0058AD25 /* ko */, + 74275ADD28478E0B0058AD25 /* nb */, + 74275ADE28478E0C0058AD25 /* pl */, + 74275ADF28478E0D0058AD25 /* pt */, + 74275AE028478E0E0058AD25 /* pt-BR */, + 74275AE128478E0F0058AD25 /* ro */, + 74275AE228478E100058AD25 /* ru */, + 74275AE328478E120058AD25 /* sk */, + 74275AE428478E130058AD25 /* es */, + 74275AE528478E140058AD25 /* sw-TZ */, + 74275AE628478E140058AD25 /* sv */, + 74275AE728478E160058AD25 /* tr */, ); name = Intents.intentdefinition; sourceTree = ""; From a96d67af158b39116763b56e2eb53000b9024a09 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Fri, 3 Jun 2022 09:37:10 +0200 Subject: [PATCH 13/15] Removed unnecessary localization key if a localization key is missing we fallback to the base which still has the key, but it's unnecessary to include in the translations as the value is just a variable. --- CryptomatorIntents/en.lproj/Intents.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/CryptomatorIntents/en.lproj/Intents.strings b/CryptomatorIntents/en.lproj/Intents.strings index 595b78814..0cc8daff3 100644 --- a/CryptomatorIntents/en.lproj/Intents.strings +++ b/CryptomatorIntents/en.lproj/Intents.strings @@ -1,7 +1,6 @@ "common.failureReason" = "Failure Reason"; "common.false" = "false"; "common.folder" = "Folder"; -"common.response.failure" = "${failureReason}"; "common.true" = "true"; "common.vault" = "Vault"; From 914bfdc253cd66f6108bc0e182e64d08c63bd605 Mon Sep 17 00:00:00 2001 From: Cryptobot Date: Tue, 7 Jun 2022 12:19:11 +0200 Subject: [PATCH 14/15] New Crowdin updates (#227) [ci skip] --- CryptomatorIntents/cs.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/de.lproj/Intents.strings | 1 - CryptomatorIntents/el.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/es.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/fr.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/hr.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/id.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/it.lproj/Intents.strings | 1 - CryptomatorIntents/nl.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/pl.lproj/Intents.strings | 12 ++++++++++++ CryptomatorIntents/ru.lproj/Intents.strings | 15 ++++++++++++++- CryptomatorIntents/sk.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/tr.lproj/Intents.strings | 18 ++++++++++++++++++ CryptomatorIntents/zh-HK.lproj/Intents.strings | 18 ++++++++++++++++++ .../zh-Hans.lproj/Intents.strings | 18 ++++++++++++++++++ .../zh-Hant.lproj/Intents.strings | 18 ++++++++++++++++++ SharedResources/hr.lproj/Localizable.strings | 12 +++++++++++- SharedResources/id.lproj/Localizable.strings | 10 ++++++++++ SharedResources/sk.lproj/Localizable.strings | 1 + SharedResources/sv.lproj/Localizable.strings | 10 ++++++++++ .../zh-HK.lproj/Localizable.strings | 10 ++++++++++ .../zh-Hant.lproj/Localizable.strings | 10 ++++++++++ 22 files changed, 294 insertions(+), 4 deletions(-) diff --git a/CryptomatorIntents/cs.lproj/Intents.strings b/CryptomatorIntents/cs.lproj/Intents.strings index d27759f3f..f0a625b26 100644 --- a/CryptomatorIntents/cs.lproj/Intents.strings +++ b/CryptomatorIntents/cs.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Důvod nezdaru"; +"common.false" = "false"; +"common.folder" = "Složka"; +"common.true" = "true"; "common.vault" = "Trezor"; + +"getFolderIntent.description" = "Vrátí objekt složky pro dané umístění v daném trezoru."; +"getFolderIntent.path" = "Popis umístění"; +"getFolderIntent.text" = "Získat složku, nacházející se v ${path} v ${vault}"; +"getFolderIntent.title" = "Získat složku"; + +"saveFileIntent.description" = "Ukládá soubor do souboru."; +"saveFileIntent.file" = "Soubor"; +"saveFileIntent.parameter.ignoreExisting" = "Ignorovat existující soubor se stejným názvem"; +"saveFileIntent.text" = "Uložit ${file} do ${folder}"; +"saveFileIntent.title" = "Uložit soubor"; + +"vaultFolder.displayName" = "Složka trezoru"; +"vaultFolder.vaultIdentifier" = "Identifikátor trezoru"; diff --git a/CryptomatorIntents/de.lproj/Intents.strings b/CryptomatorIntents/de.lproj/Intents.strings index 0affe6197..3062c386c 100644 --- a/CryptomatorIntents/de.lproj/Intents.strings +++ b/CryptomatorIntents/de.lproj/Intents.strings @@ -1,7 +1,6 @@ "common.failureReason" = "Fehlerursache"; "common.false" = "falsch"; "common.folder" = "Ordner"; -"common.response.failure" = "${failureReason}"; "common.true" = "wahr"; "common.vault" = "Tresor"; diff --git a/CryptomatorIntents/el.lproj/Intents.strings b/CryptomatorIntents/el.lproj/Intents.strings index 917e3f9ef..ddb6b5423 100644 --- a/CryptomatorIntents/el.lproj/Intents.strings +++ b/CryptomatorIntents/el.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Λόγος Αποτυχίας"; +"common.false" = "ψευδής"; +"common.folder" = "Φάκελος"; +"common.true" = "αληθής"; "common.vault" = "Κρύπτη"; + +"getFolderIntent.description" = "Επιστρέφει ένα αντικείμενο φακέλου για τη δοσμένη διαδρομή στη δοσμένη κρύπτη."; +"getFolderIntent.path" = "Διαδρομή"; +"getFolderIntent.text" = "Λήψη φακέλου που βρίσκεται σε ${path} στην ${vault}"; +"getFolderIntent.title" = "Πάρτε τον Φάκελο"; + +"saveFileIntent.description" = "Αποθηκεύει ένα αρχείο σε μια κρύπτη."; +"saveFileIntent.file" = "Αρχείο"; +"saveFileIntent.parameter.ignoreExisting" = "Παράβλεψη υπάρχοντος αρχείου με το ίδιο όνομα"; +"saveFileIntent.text" = "Αποθήκευση ${file} σε ${folder}"; +"saveFileIntent.title" = "Αποθήκευση Αρχείου"; + +"vaultFolder.displayName" = "Φάκελος Κρύπτης"; +"vaultFolder.vaultIdentifier" = "Αναγνωριστικό Κρύπτης"; diff --git a/CryptomatorIntents/es.lproj/Intents.strings b/CryptomatorIntents/es.lproj/Intents.strings index 9e1c9a038..cb0d9440b 100644 --- a/CryptomatorIntents/es.lproj/Intents.strings +++ b/CryptomatorIntents/es.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Motivo del fallo"; +"common.false" = "falso"; +"common.folder" = "Carpeta"; +"common.true" = "verdadero"; "common.vault" = "Bóveda"; + +"getFolderIntent.description" = "Devuelve un objeto de carpeta para la ruta dada en la bóveda dada."; +"getFolderIntent.path" = "Ruta"; +"getFolderIntent.text" = "Obtener carpeta ubicada en ${path} en ${vault}"; +"getFolderIntent.title" = "Obtener carpeta"; + +"saveFileIntent.description" = "Guarda un archivo en una bóveda."; +"saveFileIntent.file" = "Archivo"; +"saveFileIntent.parameter.ignoreExisting" = "Ignorar archivo existente con el mismo nombre"; +"saveFileIntent.text" = "Guardar ${file} en ${folder}"; +"saveFileIntent.title" = "Guardar archivo"; + +"vaultFolder.displayName" = "Carpeta de bóveda"; +"vaultFolder.vaultIdentifier" = "Identificador de bóveda"; diff --git a/CryptomatorIntents/fr.lproj/Intents.strings b/CryptomatorIntents/fr.lproj/Intents.strings index 212effdc0..51682f23f 100644 --- a/CryptomatorIntents/fr.lproj/Intents.strings +++ b/CryptomatorIntents/fr.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Raison de l'échec"; +"common.false" = "faux"; +"common.folder" = "Dossier"; +"common.true" = "vrai"; "common.vault" = "Coffre"; + +"getFolderIntent.description" = "Retourne un dossier pour l'emplacement donné dans le coffre donné."; +"getFolderIntent.path" = "Emplacement"; +"getFolderIntent.text" = "Récupérer le dossier situé à ${path} dans ${vault}"; +"getFolderIntent.title" = "Récupérer le Dossier"; + +"saveFileIntent.description" = "Enregistre un fichier dans un coffre."; +"saveFileIntent.file" = "Fichier"; +"saveFileIntent.parameter.ignoreExisting" = "Ignorer un fichier existant avec le même nom"; +"saveFileIntent.text" = "Enregistrer ${file} dans ${folder}"; +"saveFileIntent.title" = "Enregistrer le Fichier"; + +"vaultFolder.displayName" = "Dossier du coffre"; +"vaultFolder.vaultIdentifier" = "Identifiant du coffre"; diff --git a/CryptomatorIntents/hr.lproj/Intents.strings b/CryptomatorIntents/hr.lproj/Intents.strings index d27759f3f..36a9bd16d 100644 --- a/CryptomatorIntents/hr.lproj/Intents.strings +++ b/CryptomatorIntents/hr.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Razlog neuspjeha"; +"common.false" = "netočno"; +"common.folder" = "Mapa"; +"common.true" = "točno"; "common.vault" = "Trezor"; + +"getFolderIntent.description" = "Vraća mapu za odabranu putanju u odabranom trezoru."; +"getFolderIntent.path" = "Putanja"; +"getFolderIntent.text" = "Dohvati mapu smještenu na ${path} u ${vault}"; +"getFolderIntent.title" = "Dohvati mapu"; + +"saveFileIntent.description" = "Sprema datoteku u trezor."; +"saveFileIntent.file" = "Datoteka"; +"saveFileIntent.parameter.ignoreExisting" = "Zanemari postojeću datoteku s istim imenom"; +"saveFileIntent.text" = "Spremi ${file} u ${folder}"; +"saveFileIntent.title" = "Spremi datoteku"; + +"vaultFolder.displayName" = "Mapa trezora"; +"vaultFolder.vaultIdentifier" = "Identifikator trezora"; diff --git a/CryptomatorIntents/id.lproj/Intents.strings b/CryptomatorIntents/id.lproj/Intents.strings index c369e1822..a86bed0fa 100644 --- a/CryptomatorIntents/id.lproj/Intents.strings +++ b/CryptomatorIntents/id.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Penyebab Kegagalan"; +"common.false" = "salah"; +"common.folder" = "Folder"; +"common.true" = "benar"; "common.vault" = "Vault"; + +"getFolderIntent.description" = "Mengembalikan objek folder dari path dan vault yang telah diberikan."; +"getFolderIntent.path" = "Path"; +"getFolderIntent.text" = "Mengambil folder yang berada di ${path} di dalam ${vault}"; +"getFolderIntent.title" = "Ambil Folder"; + +"saveFileIntent.description" = "Simpan sebuah file ke vault."; +"saveFileIntent.file" = "File"; +"saveFileIntent.parameter.ignoreExisting" = "Abaikan file yang sudah ada dengan nama yang sama"; +"saveFileIntent.text" = "Simpan ${file} ke ${folder}"; +"saveFileIntent.title" = "Simpan File"; + +"vaultFolder.displayName" = "Folder Vault"; +"vaultFolder.vaultIdentifier" = "Id pengenal vault"; diff --git a/CryptomatorIntents/it.lproj/Intents.strings b/CryptomatorIntents/it.lproj/Intents.strings index 2c8fb6228..038ca002a 100644 --- a/CryptomatorIntents/it.lproj/Intents.strings +++ b/CryptomatorIntents/it.lproj/Intents.strings @@ -1,7 +1,6 @@ "common.failureReason" = "Motivo del fallimento"; "common.false" = "falso"; "common.folder" = "Cartella"; -"common.response.failure" = "${failureReason}"; "common.true" = "vero"; "common.vault" = "Cassaforte"; "getFolderIntent.path" = "Percorso"; diff --git a/CryptomatorIntents/nl.lproj/Intents.strings b/CryptomatorIntents/nl.lproj/Intents.strings index ea06747fb..8d23f10eb 100644 --- a/CryptomatorIntents/nl.lproj/Intents.strings +++ b/CryptomatorIntents/nl.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Reden van fout"; +"common.false" = "onjuist"; +"common.folder" = "Map"; +"common.true" = "juist"; "common.vault" = "Kluis"; + +"getFolderIntent.description" = "Geeft als resultaat een map object voor het opgegeven pad in de gegeven kluis."; +"getFolderIntent.path" = "Locatie"; +"getFolderIntent.text" = "Map ophalen op locatie ${path} in ${vault}"; +"getFolderIntent.title" = "Map ophalen"; + +"saveFileIntent.description" = "Slaat een bestand op naar een kluis."; +"saveFileIntent.file" = "Bestand"; +"saveFileIntent.parameter.ignoreExisting" = "Negeer bestaand bestand met dezelfde naam"; +"saveFileIntent.text" = "${file} opslaan in ${folder}"; +"saveFileIntent.title" = "Bestand opslaan"; + +"vaultFolder.displayName" = "Kluis map"; +"vaultFolder.vaultIdentifier" = "Kluis identificatie"; diff --git a/CryptomatorIntents/pl.lproj/Intents.strings b/CryptomatorIntents/pl.lproj/Intents.strings index 0c4980e06..1893b0023 100644 --- a/CryptomatorIntents/pl.lproj/Intents.strings +++ b/CryptomatorIntents/pl.lproj/Intents.strings @@ -1,7 +1,19 @@ "common.failureReason" = "Powód niepowodzenia"; +"common.false" = "nie"; "common.folder" = "Folder"; +"common.true" = "tak"; "common.vault" = "Sejf"; + +"getFolderIntent.description" = "Zwraca folder dla danej ścieżki w danym sejfie."; "getFolderIntent.path" = "Ścieżka"; "getFolderIntent.text" = "Pobierz folder znajdujący się w ${path} w ${vault}"; "getFolderIntent.title" = "Pobierz folder"; + +"saveFileIntent.description" = "Zapisuje plik w sejfie."; "saveFileIntent.file" = "Plik"; +"saveFileIntent.parameter.ignoreExisting" = "Ignoruj istniejący plik o tej samej nazwie"; +"saveFileIntent.text" = "Zapisz ${file} do ${folder}"; +"saveFileIntent.title" = "Zapisz plik"; + +"vaultFolder.displayName" = "Folder sejfu"; +"vaultFolder.vaultIdentifier" = "Identyfikator sejfu"; diff --git a/CryptomatorIntents/ru.lproj/Intents.strings b/CryptomatorIntents/ru.lproj/Intents.strings index 72cab1c0e..773825125 100644 --- a/CryptomatorIntents/ru.lproj/Intents.strings +++ b/CryptomatorIntents/ru.lproj/Intents.strings @@ -1,6 +1,19 @@ "common.failureReason" = "Причина ошибки"; "common.false" = "ложь"; "common.folder" = "Папка"; -"common.response.failure" = "${failureReason}"; "common.true" = "истина"; "common.vault" = "Хранилище"; + +"getFolderIntent.description" = "Возвращает объект папки для заданного пути в указанном хранилище."; +"getFolderIntent.path" = "Путь"; +"getFolderIntent.text" = "Получить папку, расположенную в ${path} в ${vault}"; +"getFolderIntent.title" = "Получить папку"; + +"saveFileIntent.description" = "Сохраняет файл в хранилище."; +"saveFileIntent.file" = "Файл"; +"saveFileIntent.parameter.ignoreExisting" = "Игнорировать существующий файл с таким же именем"; +"saveFileIntent.text" = "Сохранить ${file} в ${folder}"; +"saveFileIntent.title" = "Сохранить файл"; + +"vaultFolder.displayName" = "Папка хранилища"; +"vaultFolder.vaultIdentifier" = "Идентификатор хранилища"; diff --git a/CryptomatorIntents/sk.lproj/Intents.strings b/CryptomatorIntents/sk.lproj/Intents.strings index d27759f3f..f594241fe 100644 --- a/CryptomatorIntents/sk.lproj/Intents.strings +++ b/CryptomatorIntents/sk.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Dôvod zlyhania"; +"common.false" = "nepravda"; +"common.folder" = "Priečinok"; +"common.true" = "pravda"; "common.vault" = "Trezor"; + +"getFolderIntent.description" = "Vrátiť objekt priečinka pre danú cestu v davom trezore."; +"getFolderIntent.path" = "Cesta"; +"getFolderIntent.text" = "Vziať priečinok umiestnený ${path} v ${vault}"; +"getFolderIntent.title" = "Získať priečinok"; + +"saveFileIntent.description" = "Uložiť súbory do trezoru."; +"saveFileIntent.file" = "Súbor"; +"saveFileIntent.parameter.ignoreExisting" = "Ignorovať existujúci súbor s rovnakým názvom"; +"saveFileIntent.text" = "Uložiť ${file} do ${folder}"; +"saveFileIntent.title" = "Uložiť súbor"; + +"vaultFolder.displayName" = "Priečinok trezora"; +"vaultFolder.vaultIdentifier" = "Identifikátor trezora"; diff --git a/CryptomatorIntents/tr.lproj/Intents.strings b/CryptomatorIntents/tr.lproj/Intents.strings index c3ca13058..07b1bf662 100644 --- a/CryptomatorIntents/tr.lproj/Intents.strings +++ b/CryptomatorIntents/tr.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "Başarısızlık Nedeni"; +"common.false" = "yanlış"; +"common.folder" = "Klasör"; +"common.true" = "doğru"; "common.vault" = "Kasa"; + +"getFolderIntent.description" = "Sağlanan kasada gösterilen yol için bir klasör nesnesi döndürür."; +"getFolderIntent.path" = "Yol"; +"getFolderIntent.text" = "${vault} içinde ${path} konumunda bulunan klasörü al"; +"getFolderIntent.title" = "Klasörü Al"; + +"saveFileIntent.description" = "Bir dosyayı kasaya kaydeder."; +"saveFileIntent.file" = "Dosya"; +"saveFileIntent.parameter.ignoreExisting" = "Aynı ada sahip mevcut dosyayı yoksay"; +"saveFileIntent.text" = "${file} dosyasını ${folder} klasörüne kaydet"; +"saveFileIntent.title" = "Dosyayı kaydet"; + +"vaultFolder.displayName" = "Kasa Klasörü"; +"vaultFolder.vaultIdentifier" = "Kasa Tanımlayıcısı"; diff --git a/CryptomatorIntents/zh-HK.lproj/Intents.strings b/CryptomatorIntents/zh-HK.lproj/Intents.strings index 50d3b3bb8..a911e1c46 100644 --- a/CryptomatorIntents/zh-HK.lproj/Intents.strings +++ b/CryptomatorIntents/zh-HK.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "失敗原因"; +"common.false" = "否"; +"common.folder" = "資料夾"; +"common.true" = "是"; "common.vault" = "加密庫"; + +"getFolderIntent.description" = "返回特定加密庫內指定路徑的資料夾。"; +"getFolderIntent.path" = "路徑"; +"getFolderIntent.text" = "取得 ${vault} 內位於 ${path} 的資料夾"; +"getFolderIntent.title" = "取得資料夾"; + +"saveFileIntent.description" = "儲存檔案到加密庫。"; +"saveFileIntent.file" = "檔案"; +"saveFileIntent.parameter.ignoreExisting" = "忽略同名的現存文件"; +"saveFileIntent.text" = "儲存 ${file} 到 ${folder}"; +"saveFileIntent.title" = "儲存檔案"; + +"vaultFolder.displayName" = "加密庫資料夾"; +"vaultFolder.vaultIdentifier" = "加密庫識別碼"; diff --git a/CryptomatorIntents/zh-Hans.lproj/Intents.strings b/CryptomatorIntents/zh-Hans.lproj/Intents.strings index 4a1575369..1cda46113 100644 --- a/CryptomatorIntents/zh-Hans.lproj/Intents.strings +++ b/CryptomatorIntents/zh-Hans.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "失败原因"; +"common.false" = "false"; +"common.folder" = "文件夹"; +"common.true" = "true"; "common.vault" = "保险库"; + +"getFolderIntent.description" = "返回给定保险库中给定路径的文件夹对象"; +"getFolderIntent.path" = "路径"; +"getFolderIntent.text" = "获取位于“${vault}”中 ${path} 的文件夹"; +"getFolderIntent.title" = "获取文件夹"; + +"saveFileIntent.description" = "保存文件到保险库"; +"saveFileIntent.file" = "文件"; +"saveFileIntent.parameter.ignoreExisting" = "忽略同名的现有文件"; +"saveFileIntent.text" = "保存 ${file} 到 ${folder}"; +"saveFileIntent.title" = "保存文件"; + +"vaultFolder.displayName" = "保险库文件夹"; +"vaultFolder.vaultIdentifier" = "保险库标识符"; diff --git a/CryptomatorIntents/zh-Hant.lproj/Intents.strings b/CryptomatorIntents/zh-Hant.lproj/Intents.strings index 788a63312..50dd011dd 100644 --- a/CryptomatorIntents/zh-Hant.lproj/Intents.strings +++ b/CryptomatorIntents/zh-Hant.lproj/Intents.strings @@ -1 +1,19 @@ +"common.failureReason" = "失敗原因"; +"common.false" = "否"; +"common.folder" = "資料夾"; +"common.true" = "是"; "common.vault" = "加密檔案庫"; + +"getFolderIntent.description" = "返回特定加密檔案庫內指定路徑的資料夾。"; +"getFolderIntent.path" = "路徑"; +"getFolderIntent.text" = "取得 ${vault} 內位於 ${path} 的資料夾"; +"getFolderIntent.title" = "取得資料夾"; + +"saveFileIntent.description" = "儲存檔案到加密檔案庫。"; +"saveFileIntent.file" = "檔案"; +"saveFileIntent.parameter.ignoreExisting" = "忽略同名的現存文件"; +"saveFileIntent.text" = "儲存 ${file} 到 ${folder}"; +"saveFileIntent.title" = "儲存檔案"; + +"vaultFolder.displayName" = "加密檔案庫資料夾"; +"vaultFolder.vaultIdentifier" = "加密檔案庫識別碼"; diff --git a/SharedResources/hr.lproj/Localizable.strings b/SharedResources/hr.lproj/Localizable.strings index 5028dd650..5facb7334 100644 --- a/SharedResources/hr.lproj/Localizable.strings +++ b/SharedResources/hr.lproj/Localizable.strings @@ -37,7 +37,7 @@ "accountList.signOut.alert.message" = "Odjavom, svi povezani trezori bit će uklonjeni s popisa trezora. Šifrirani podaci neće biti izbrisani. Možete se ponovno prijaviti i kasnije ponovo dodati trezore."; "addVault.title" = "Dodaj trezor"; -"addVault.createNewVault.title" = "Napravi novi trezor"; +"addVault.createNewVault.title" = "Izradi novi trezor"; "addVault.createNewVault.purchase" = "Izrada novog trezora zahtjeva punu verziju Cryptomator-a."; "addVault.createNewVault.setVaultName.header.title" = "Odaberite ime za trezor."; "addVault.createNewVault.setVaultName.cells.name" = "Ime trezora"; @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Otključaj"; "fileProvider.clearFileFromCache.title" = "Očisti datoteku iz predmemorije"; "fileProvider.clearFileFromCache.message" = "Ovo samo uklanja lokalnu datoteku s vašeg uređaja, a ne briše datoteku u oblaku."; +"fileProvider.fileImporting.error.missingPremium" = "Otključajte punu verziju u aplikaciji Cryptomator da biste dobili pristup za pisanje u svoje trezore."; "fileProvider.uploadProgress.connecting" = "Spajanje…"; "fileProvider.uploadProgress.message" = "Trenutni napredak: %@\n\nAko primijetite da je napredak učitavanja zapeo, možete ponovno pokušati s prijenosom."; "fileProvider.uploadProgress.missing" = "Napredak se ne može utvrditi. Možda još uvijek radi u pozadini."; "fileProvider.uploadProgress.title" = "Učitavanje…"; "fileProvider.uploadProgress.missingDomainError" = "Nije moguće pronaći domenu."; +"getFolderIntent.error.missingPath" = "Putanja nije navedena. Navedite valjanu putanju za koju treba vratiti mapu."; +"getFolderIntent.error.noVaultSelected" = "Niti jedan trezor nije odabran."; +"intents.saveFile.missingFile" = "Datoteka nije ispravna."; +"intents.saveFile.invalidFolder" = "Mapa nije ispravna."; +"intents.saveFile.missingTemporaryFolder" = "Izrada privremene mape nije uspjela."; +"intents.saveFile.lockedVault" = "Morate otključati svoj trezor da biste koristili ovaj prečac."; +"intents.saveFile.selectedVaultNotFound" = "Odabrani trezor nije pronađen."; + "keepUnlocked.alert.title" = "Zaključati trezor?"; "keepUnlocked.alert.message" = "Ova promjena zahtijeva da vaš trezor bude zaključan kako bi stupila na snagu."; "keepUnlocked.alert.confirm" = "Potvrdi i zaključaj sada"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Upravljaj pretplatom"; "settings.rateApp" = "Ocijeni aplikaciju"; "settings.sendLogFile" = "Pošalji log datoteku"; +"settings.shortcutsGuide" = "Vodič za prečace"; "settings.unlockFullVersion" = "Otključaj punu verziju"; "snapshots.fileprovider.file1" = "/Računovodstvo.brojevi"; diff --git a/SharedResources/id.lproj/Localizable.strings b/SharedResources/id.lproj/Localizable.strings index ecccd80e7..6bb33c998 100644 --- a/SharedResources/id.lproj/Localizable.strings +++ b/SharedResources/id.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Buka Kunci"; "fileProvider.clearFileFromCache.title" = "Hapus File dari Cache"; "fileProvider.clearFileFromCache.message" = "Ini hanya akan menghapus file lokal di perangkat Anda dan tidak akan menghapus file yang ada di cloud."; +"fileProvider.fileImporting.error.missingPremium" = "Buka versi lengkap di aplikasi Cryptomator untuk mendapatkan akses tulis ke vault Anda."; "fileProvider.uploadProgress.connecting" = "Menyambungkan…"; "fileProvider.uploadProgress.message" = "Kemajuan Proses Saat Ini: %@\n\nJika proses Upload tidak mengalami peningkatan, Anda bisa mencoba unggah kembali."; "fileProvider.uploadProgress.missing" = "Kemajuan proses tidak dapat ditentukan. Kemungkinan masih berjalan di latar belakang."; "fileProvider.uploadProgress.title" = "Mengunggah…"; "fileProvider.uploadProgress.missingDomainError" = "Tidak dapat menemukan domain."; +"getFolderIntent.error.missingPath" = "Tidak ada path yang disediakan. Harap cantumkan path yang valid untuk folder yang harus dikembalikan."; +"getFolderIntent.error.noVaultSelected" = "Tidak ada vault yang dipilih."; +"intents.saveFile.missingFile" = "File yang disediakan tidak valid."; +"intents.saveFile.invalidFolder" = "Folder yang disediakan tidak valid."; +"intents.saveFile.missingTemporaryFolder" = "Gagal membuat folder sementara."; +"intents.saveFile.lockedVault" = "Anda perlu membuka vault untuk menggunakan shortcut ini."; +"intents.saveFile.selectedVaultNotFound" = "Vault terpilih tidak dapat ditemukan."; + "keepUnlocked.alert.title" = "Kunci Vault?"; "keepUnlocked.alert.message" = "Perubahan berikut memerlukan vault Anda dalam kondisi terbuka agar bisa diterapkan."; "keepUnlocked.alert.confirm" = "Konfirmasi & Kunci Sekarang"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Atur Langganan"; "settings.rateApp" = "Beri Rating"; "settings.sendLogFile" = "Kirim File Log"; +"settings.shortcutsGuide" = "Panduan Shortcut"; "settings.unlockFullVersion" = "Buka Versi Lengkap"; "snapshots.fileprovider.file1" = "/Akuntansi.numbers"; diff --git a/SharedResources/sk.lproj/Localizable.strings b/SharedResources/sk.lproj/Localizable.strings index be8dbf344..3f620d0c8 100644 --- a/SharedResources/sk.lproj/Localizable.strings +++ b/SharedResources/sk.lproj/Localizable.strings @@ -265,6 +265,7 @@ "vaultList.remove.alert.message" = "Toto len odstráni trezor zo zoznamu trezorov. Žiadne zašifrované dáta sa nezmažú. Môžte znovu pridať trezor v budúcnosti."; "vaultProviderFactory.error.unsupportedVaultConfig" = "Konfigurácia trezora je nepodporovaná. Prosím uistite sa či spúšťate najnovšiu verziu Cryptomator-a."; +"vaultProviderFactory.error.unsupportedVaultVersion" = "Verzia trezora %ld je nepodporovaná. Tento trezor bol vytvorený staršou alebo novšou verziou Cryptomatora."; "webDAVAuthentication.progress" = "Overovanie…"; "webDAVAuthentication.httpConnection.alert.title" = "Použiť HTTPS?"; diff --git a/SharedResources/sv.lproj/Localizable.strings b/SharedResources/sv.lproj/Localizable.strings index f923cc6ad..9b933e2b9 100644 --- a/SharedResources/sv.lproj/Localizable.strings +++ b/SharedResources/sv.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "Lås upp"; "fileProvider.clearFileFromCache.title" = "Ta bort fil från buffert"; "fileProvider.clearFileFromCache.message" = "Detta tar bara bort den lokala filen från din enhet och tar inte bort filen i molnet."; +"fileProvider.fileImporting.error.missingPremium" = "Lås upp den fullständiga versionen i Cryptomator-appen för att få skrivtillgång till dina valv."; "fileProvider.uploadProgress.connecting" = "Ansluter…"; "fileProvider.uploadProgress.message" = "Status: %@\n\nOm du märker att uppladdningsprocessen har fastnat kan du försöka ladda upp den igen."; "fileProvider.uploadProgress.missing" = "Oklart om det fungerade. Processen kan fortfarande köras i bakgrunden."; "fileProvider.uploadProgress.title" = "Laddar upp…"; "fileProvider.uploadProgress.missingDomainError" = "Kunde inte hitta domänen."; +"getFolderIntent.error.missingPath" = "Ingen sökväg angavs. Vänligen ange en giltig sökväg för vilken en mapp ska returneras."; +"getFolderIntent.error.noVaultSelected" = "Inget valv har valts."; +"intents.saveFile.missingFile" = "Den angivna filen är inte giltig."; +"intents.saveFile.invalidFolder" = "Den angivna mappen är inte giltig."; +"intents.saveFile.missingTemporaryFolder" = "Det gick inte att skapa en temporär mapp."; +"intents.saveFile.lockedVault" = "Du måste låsa upp ditt valv för att kunna använda denna genväg."; +"intents.saveFile.selectedVaultNotFound" = "Valt valv kunde inte hittas."; + "keepUnlocked.alert.title" = "Lås valv?"; "keepUnlocked.alert.message" = "Denna ändring kräver att ditt valv låses för att träda i kraft."; "keepUnlocked.alert.confirm" = "Bekräfta och lås nu"; @@ -180,6 +189,7 @@ "settings.manageSubscriptions" = "Hantera prenumeration"; "settings.rateApp" = "Betygsätt app"; "settings.sendLogFile" = "Skicka loggfil"; +"settings.shortcutsGuide" = "Vägledning till genvägar"; "settings.unlockFullVersion" = "Lås upp fullversion"; "snapshots.fileprovider.file1" = "/Budget.numbers"; diff --git a/SharedResources/zh-HK.lproj/Localizable.strings b/SharedResources/zh-HK.lproj/Localizable.strings index 24752fd1e..d9ff8e91e 100644 --- a/SharedResources/zh-HK.lproj/Localizable.strings +++ b/SharedResources/zh-HK.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "解鎖"; "fileProvider.clearFileFromCache.title" = "清除快取檔䅁"; "fileProvider.clearFileFromCache.message" = "這只會從本機上刪除文件,而不會刪除雲端的文件。"; +"fileProvider.fileImporting.error.missingPremium" = "解鎖 Cryptomator 完整版本以獲得寫入加密庫的功能。"; "fileProvider.uploadProgress.connecting" = "連接中…"; "fileProvider.uploadProgress.message" = "目前進度:%@\n\n如果你注意到上傳進度卡住,可以重試上傳。"; "fileProvider.uploadProgress.missing" = "無法確定目前進度。它可能仍在背景運行。"; "fileProvider.uploadProgress.title" = "上載中..."; "fileProvider.uploadProgress.missingDomainError" = "找不到域名。"; +"getFolderIntent.error.missingPath" = "沒有提供路徑。請提供應返回資料夾的有效路徑。"; +"getFolderIntent.error.noVaultSelected" = "未選擇任何加密庫。"; +"intents.saveFile.missingFile" = "提供的檔案無效。"; +"intents.saveFile.invalidFolder" = "提供的資料夾無效。"; +"intents.saveFile.missingTemporaryFolder" = "無法建立臨時資料夾。"; +"intents.saveFile.lockedVault" = "你要先解鎖加密庫後,才能使用此捷徑。"; +"intents.saveFile.selectedVaultNotFound" = "找不到選定的加密庫。"; + "keepUnlocked.alert.title" = "鎖定加密庫?"; "keepUnlocked.alert.message" = "此更改需要鎖定加密庫後才能生效。"; "keepUnlocked.alert.confirm" = "確認並立即鎖定"; @@ -181,6 +190,7 @@ "settings.manageSubscriptions" = "管理訂閱"; "settings.rateApp" = "為應用程式評分"; "settings.sendLogFile" = "傳送日誌檔案"; +"settings.shortcutsGuide" = "捷徑使用手冊"; "settings.unlockFullVersion" = "解鎖完整版"; "snapshots.fileprovider.file1" = "/財務.numbers"; diff --git a/SharedResources/zh-Hant.lproj/Localizable.strings b/SharedResources/zh-Hant.lproj/Localizable.strings index a1fe23f27..ff5a96d67 100644 --- a/SharedResources/zh-Hant.lproj/Localizable.strings +++ b/SharedResources/zh-Hant.lproj/Localizable.strings @@ -100,12 +100,21 @@ "fileProvider.error.unlockButton" = "解鎖"; "fileProvider.clearFileFromCache.title" = "清除快取檔䅁"; "fileProvider.clearFileFromCache.message" = "這只會從您的本機上刪除文件,而不會刪除雲端的文件。"; +"fileProvider.fileImporting.error.missingPremium" = "解鎖 Cryptomator 完整版本以獲得寫入加密檔案庫的功能。"; "fileProvider.uploadProgress.connecting" = "連線中…"; "fileProvider.uploadProgress.message" = "目前進度:%@\n\n如果您注意到上傳進度卡住,您可以重試上傳。"; "fileProvider.uploadProgress.missing" = "無法確定目前進度。它可能仍在後台運行。"; "fileProvider.uploadProgress.title" = "上傳中…"; "fileProvider.uploadProgress.missingDomainError" = "找不到域名。"; +"getFolderIntent.error.missingPath" = "沒有提供路徑。請提供應返回資料夾的有效路徑。"; +"getFolderIntent.error.noVaultSelected" = "未選擇任何加密檔案庫。"; +"intents.saveFile.missingFile" = "提供的檔案無效。"; +"intents.saveFile.invalidFolder" = "提供的資料夾無效。"; +"intents.saveFile.missingTemporaryFolder" = "無法建立臨時資料夾。"; +"intents.saveFile.lockedVault" = "您需要解鎖加密檔案庫後,才能使用此捷徑。"; +"intents.saveFile.selectedVaultNotFound" = "找不到選定的加密檔案庫。"; + "keepUnlocked.alert.title" = "鎖定加密檔案庫?"; "keepUnlocked.alert.message" = "此項更改需要鎖定加密檔案庫後才能生效"; "keepUnlocked.alert.confirm" = "確認並立即鎖定"; @@ -181,6 +190,7 @@ "settings.manageSubscriptions" = "管理訂閱"; "settings.rateApp" = "為應用程式評分"; "settings.sendLogFile" = "傳送日誌檔案"; +"settings.shortcutsGuide" = "捷徑使用手冊"; "settings.unlockFullVersion" = "解鎖完整版"; "snapshots.fileprovider.file1" = "/財務.numbers"; From ebdd60084c15e3c32239c08690cd82320ed8a8d0 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 7 Jun 2022 12:22:00 +0200 Subject: [PATCH 15/15] Updated release notes [ci skip] --- fastlane/changelog.txt | 4 +++- fastlane/metadata/de-DE/release_notes.txt | 4 +++- fastlane/metadata/en-US/release_notes.txt | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 63693234c..1871150e8 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1,2 +1,4 @@ - Added Shortcuts integration, which allows you to create automations like "auto photo upload" (#56, #114, #222) -- Improved vault format 8 compatibility (#198, cryptolib-swift#9) \ No newline at end of file +- Improved vault format 8 compatibility (#198, cryptolib-swift#9) + +Make sure to check out our Shortcuts Guide, which you can find in the settings of Cryptomator. \ No newline at end of file diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt index 8cdd7c6ad..da2b0a0db 100644 --- a/fastlane/metadata/de-DE/release_notes.txt +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -1,2 +1,4 @@ - Kurzbefehle-Integration hinzugefügt, die es erlaubt, Automatisierungen wie "automatischer Foto-Upload" zu erstellen (#56, #114, #222) -- Kompatibilität mit Tresorformat 8 verbessert (#198, cryptolib-swift#9) \ No newline at end of file +- Kompatibilität mit Tresorformat 8 verbessert (#198, cryptolib-swift#9) + +Schau dir unbedingt unseren Leitfaden für Kurzbefehle an, den du in den Einstellungen von Cryptomator finden kannst. \ No newline at end of file diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 63693234c..1871150e8 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,2 +1,4 @@ - Added Shortcuts integration, which allows you to create automations like "auto photo upload" (#56, #114, #222) -- Improved vault format 8 compatibility (#198, cryptolib-swift#9) \ No newline at end of file +- Improved vault format 8 compatibility (#198, cryptolib-swift#9) + +Make sure to check out our Shortcuts Guide, which you can find in the settings of Cryptomator. \ No newline at end of file