diff --git a/App/gl.lproj/InfoPlist.strings b/App/gl.lproj/InfoPlist.strings new file mode 100644 index 0000000..ae3c0ea --- /dev/null +++ b/App/gl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Escanear código QR"; +"CFBundleDisplayName" = "Contrasinais"; +"NSPhotoLibraryAddUsageDescription" = "Compartir código QR"; +"NSFaceIDUsageDescription" = "Desbloquear a aplicación"; diff --git a/App/pl.lproj/InfoPlist.strings b/App/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000..be37623 --- /dev/null +++ b/App/pl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSPhotoLibraryAddUsageDescription" = "Udostępnij kod QR"; +"CFBundleDisplayName" = "Hasła"; +"NSCameraUsageDescription" = "Skanuj kodu QR"; +"NSFaceIDUsageDescription" = "Odblokuj aplikację"; diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e69bf..885c482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [v2.7.4] - 2024-08-29 +- Galician translation (Thanks Miguel A. Bouzada!) +- Polish translation (Thanks Radosław Rudner!) +- Optimizations + ## [v2.7.3] - 2024-07-25 - Ukrainian translation (Thanks Markevych Dmytro!) - Upgraded more parts of the app to new architecture diff --git a/Extension/gl.lproj/InfoPlist.strings b/Extension/gl.lproj/InfoPlist.strings new file mode 100644 index 0000000..e0b0422 --- /dev/null +++ b/Extension/gl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"CFBundleDisplayName" = "Insira o contrasinal dun só uso"; +"NSCameraUsageDescription" = "Escanear código QR"; +"NSFaceIDUsageDescription" = "Desbloquear a aplicación"; +"NSPhotoLibraryAddUsageDescription" = "Compartir código QR"; diff --git a/Extension/pl.lproj/InfoPlist.strings b/Extension/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000..c4151e6 --- /dev/null +++ b/Extension/pl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSPhotoLibraryAddUsageDescription" = "Udostępnij kod QR"; +"CFBundleDisplayName" = "Wstaw jednorazowe hasło"; +"NSCameraUsageDescription" = "Skanuj kodu QR"; +"NSFaceIDUsageDescription" = "Odblokuj aplikację"; diff --git a/Passwords.xcodeproj/project.pbxproj b/Passwords.xcodeproj/project.pbxproj index d062dbd..d99dea3 100644 --- a/Passwords.xcodeproj/project.pbxproj +++ b/Passwords.xcodeproj/project.pbxproj @@ -269,6 +269,7 @@ DD2D701028C7ED1F000F1607 /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2D700F28C7ED1F000F1607 /* FailableDecodable.swift */; }; DD2D701128C7ED1F000F1607 /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2D700F28C7ED1F000F1607 /* FailableDecodable.swift */; }; DD2D701228C7ED1F000F1607 /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2D700F28C7ED1F000F1607 /* FailableDecodable.swift */; }; + DD3634042C4EA9FB00B5AC57 /* Array+Initializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3634032C4EA9FB00B5AC57 /* Array+Initializable.swift */; }; DD38ACF927DCC3B600E13648 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD38ACF827DCC3B600E13648 /* PasswordGenerator.swift */; }; DD38ACFA27DCC3B600E13648 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD38ACF827DCC3B600E13648 /* PasswordGenerator.swift */; }; DD38ACFB27DCC3B600E13648 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD38ACF827DCC3B600E13648 /* PasswordGenerator.swift */; }; @@ -359,6 +360,8 @@ DD76068C295F731200109075 /* Dismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD76068B295F731200109075 /* Dismiss.swift */; }; DD76068D295F731200109075 /* Dismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD76068B295F731200109075 /* Dismiss.swift */; }; DD76068E295F731200109075 /* Dismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD76068B295F731200109075 /* Dismiss.swift */; }; + DD77FF3E2C4E3E0300E193C9 /* Associating.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77FF3D2C4E3E0300E193C9 /* Associating.swift */; }; + DD77FF402C4E3E2900E193C9 /* Initializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77FF3F2C4E3E2900E193C9 /* Initializable.swift */; }; DD7815FC2C2420DB00D2924F /* AsyncSequence+First.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7815FB2C2420D400D2924F /* AsyncSequence+First.swift */; }; DD7815FD2C2420DB00D2924F /* AsyncSequence+First.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7815FB2C2420D400D2924F /* AsyncSequence+First.swift */; }; DD7815FE2C2420DB00D2924F /* AsyncSequence+First.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7815FB2C2420D400D2924F /* AsyncSequence+First.swift */; }; @@ -1009,6 +1012,7 @@ DD34DEEC2828F592004DA186 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/InfoPlist.strings"; sourceTree = ""; }; DD34DEED2828F5A3004DA186 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/InfoPlist.strings"; sourceTree = ""; }; DD34DEEE2828F5AF004DA186 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/InfoPlist.strings"; sourceTree = ""; }; + DD3634032C4EA9FB00B5AC57 /* Array+Initializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Initializable.swift"; sourceTree = ""; }; DD38ACF827DCC3B600E13648 /* PasswordGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = ""; }; DD3A993827FDD9AA000E1938 /* Icon.svg */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Icon.svg; sourceTree = ""; }; DD3FAD602775DC54002202DE /* ListSettingsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListSettingsRequest.swift; sourceTree = ""; }; @@ -1030,6 +1034,10 @@ DD57399C2A1031C9004B6E69 /* QRCodeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeService.swift; sourceTree = ""; }; DD5862A32C08A90F00AB7343 /* Actionable+LogFunctionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Actionable+LogFunctionCall.swift"; sourceTree = ""; }; DD5862AE2C08AEAE00AB7343 /* ServerSetupViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSetupViewModelMock.swift; sourceTree = ""; }; + DD5A1B482C77CFFD00B57205 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD5A1B492C77CFFD00B57205 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD5A1B4A2C77CFFD00B57205 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD5A1B4B2C77CFFD00B57205 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; DD5A73A527E5F87400CC90E4 /* ObservableObject+ObjectDidChangeRecently.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableObject+ObjectDidChangeRecently.swift"; sourceTree = ""; }; DD5AECA727B1150B002DAA87 /* OTP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTP.swift; sourceTree = ""; }; DD5B586A27D00E6F00E54E71 /* ShareOTPPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareOTPPage.swift; sourceTree = ""; }; @@ -1062,6 +1070,8 @@ DD760683295E498000109075 /* PropertyAccessLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyAccessLogging.swift; sourceTree = ""; }; DD760687295F72BC00109075 /* Sync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sync.swift; sourceTree = ""; }; DD76068B295F731200109075 /* Dismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dismiss.swift; sourceTree = ""; }; + DD77FF3D2C4E3E0300E193C9 /* Associating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Associating.swift; sourceTree = ""; }; + DD77FF3F2C4E3E2900E193C9 /* Initializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initializable.swift; sourceTree = ""; }; DD7815FB2C2420D400D2924F /* AsyncSequence+First.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncSequence+First.swift"; sourceTree = ""; }; DD78D6CD2AAC77A200854599 /* LegacySafeAreaPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySafeAreaPadding.swift; sourceTree = ""; }; DD78D6D12AACDB3100854599 /* WindowSizeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSizeService.swift; sourceTree = ""; }; @@ -1100,6 +1110,10 @@ DD85E22F2C2A140000EDBC9C /* Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeout.swift; sourceTree = ""; }; DD889A1A27DB4AB40021AF5F /* OnSizeChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnSizeChange.swift; sourceTree = ""; }; DD8A77D22C2774430086CFDD /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; + DD8CE44B2C753C90002A6C1C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD8CE44C2C753C90002A6C1C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD8CE44D2C753C90002A6C1C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/InfoPlist.strings; sourceTree = ""; }; + DD8CE44E2C753C90002A6C1C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = ""; }; DD8DC0472C0866480030FF6E /* Stateful+LogPropertyAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stateful+LogPropertyAccess.swift"; sourceTree = ""; }; DD941A322AF8419F002D263E /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; DD941A332AF84293002D263E /* .xcode-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".xcode-version"; sourceTree = ""; }; @@ -1983,6 +1997,14 @@ path = "en-US"; sourceTree = ""; }; + DD3634022C4EA9EB00B5AC57 /* Conformance */ = { + isa = PBXGroup; + children = ( + DD3634032C4EA9FB00B5AC57 /* Array+Initializable.swift */, + ); + path = Conformance; + sourceTree = ""; + }; DD42600B293B736E004A77AB /* AppConfig */ = { isa = PBXGroup; children = ( @@ -2131,6 +2153,7 @@ DD5E3CBB2954571B0032F517 /* Extensions */ = { isa = PBXGroup; children = ( + DD3634022C4EA9EB00B5AC57 /* Conformance */, DD1256682C2C8FDD00406836 /* Convenience */, DD1256672C2C8FC700406836 /* Mocking */, DD1256662C2C8F9C00406836 /* Randomizers */, @@ -2166,9 +2189,19 @@ path = Mocks; sourceTree = ""; }; + DD77FF3C2C4E3DFA00E193C9 /* Architecture */ = { + isa = PBXGroup; + children = ( + DD77FF3D2C4E3E0300E193C9 /* Associating.swift */, + DD77FF3F2C4E3E2900E193C9 /* Initializable.swift */, + ); + path = Architecture; + sourceTree = ""; + }; DD7BC51929AFBAC5004AA7BC /* Utilities */ = { isa = PBXGroup; children = ( + DD77FF3C2C4E3DFA00E193C9 /* Architecture */, DD1256622C2C8F5200406836 /* Convenience */, DD1256602C2C8F2F00406836 /* Mocking */, ); @@ -2652,6 +2685,8 @@ ca, sv, uk, + gl, + pl, ); mainGroup = 8F118746253C9EE10019FECA; packageReferences = ( @@ -3298,6 +3333,7 @@ DD5419712A08D28B004C3EFA /* URL+Random.swift in Sources */, DDF0C4922ADD430200A50661 /* PasteboardServiceTests.swift in Sources */, DD845CC02AF3B87500048F3F /* WindowSizeServiceMock.swift in Sources */, + DD77FF402C4E3E2900E193C9 /* Initializable.swift in Sources */, DDF423832A9BEACB0060C5E8 /* ObjectMock.swift in Sources */, DDDD93F72A14BD650061BEA0 /* QRCodeServiceTests.swift in Sources */, DD7BC51E29AFFCCF004AA7BC /* TagValidationServiceMock.swift in Sources */, @@ -3361,11 +3397,13 @@ DD24F0392A8E23E2009823C8 /* PurchaseServiceMock.swift in Sources */, DD7BC51C29AFBAD8004AA7BC /* Mock.swift in Sources */, DDF0C4872ADD3EC300A50661 /* PasteboardRepositoryMock.swift in Sources */, + DD77FF3E2C4E3E0300E193C9 /* Associating.swift in Sources */, DD4628292A9407F7002DB487 /* ErrorMock.swift in Sources */, DDF423842A9BEC350060C5E8 /* Collection+Subscript.swift in Sources */, DD46282B2A948F87002DB487 /* Finish.swift in Sources */, DDFF5D4829D8443B00422603 /* Container+AutoRegistering.swift in Sources */, DD0B7AEF2972CEBD00A59E3C /* FolderValidationServiceTests.swift in Sources */, + DD3634042C4EA9FB00B5AC57 /* Array+Initializable.swift in Sources */, DDC6E1A229F085D100607C81 /* PasteboardServiceMock.swift in Sources */, DD7BC52A29AFFE43004AA7BC /* ViewModelMock.swift in Sources */, DDF857012C2F6674002F2581 /* CheckTrustUseCaseMock.swift in Sources */, @@ -3640,6 +3678,8 @@ DD0424682829858900E9B9DE /* ca */, DDAF982829910BA7005D062D /* sv */, DDDBF1E72C3D50DD0036B94C /* uk */, + DD8CE44E2C753C90002A6C1C /* gl */, + DD5A1B4B2C77CFFD00B57205 /* pl */, ); name = Localizable.strings; sourceTree = ""; @@ -3656,6 +3696,8 @@ DD0424652829853100E9B9DE /* ca */, DDAF982529910B86005D062D /* sv */, DDDBF1E42C3D50DD0036B94C /* uk */, + DD8CE44B2C753C90002A6C1C /* gl */, + DD5A1B482C77CFFD00B57205 /* pl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3672,6 +3714,8 @@ DD0424662829853A00E9B9DE /* ca */, DDAF982629910B8F005D062D /* sv */, DDDBF1E52C3D50DD0036B94C /* uk */, + DD8CE44C2C753C90002A6C1C /* gl */, + DD5A1B492C77CFFD00B57205 /* pl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3688,6 +3732,8 @@ DD0424672829854900E9B9DE /* ca */, DDAF982729910B9A005D062D /* sv */, DDDBF1E62C3D50DD0036B94C /* uk */, + DD8CE44D2C753C90002A6C1C /* gl */, + DD5A1B4A2C77CFFD00B57205 /* pl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3825,7 +3871,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; CODE_SIGN_ENTITLEMENTS = App/Passwords.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 3L4U93393E; ENABLE_PREVIEWS = YES; @@ -3835,7 +3881,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = NO; @@ -3852,7 +3898,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; CODE_SIGN_ENTITLEMENTS = App/Passwords.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 3L4U93393E; ENABLE_PREVIEWS = YES; @@ -3862,7 +3908,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = NO; @@ -3876,7 +3922,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Provider/Provider.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = 3L4U93393E; INFOPLIST_FILE = Provider/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -3885,7 +3931,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER).Provider"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -3899,7 +3945,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Provider/Provider.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = 3L4U93393E; INFOPLIST_FILE = Provider/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -3908,7 +3954,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER).Provider"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4021,7 +4067,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = ExtensionIcon; CODE_SIGN_ENTITLEMENTS = Extension/Extension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = 3L4U93393E; INFOPLIST_FILE = Extension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -4030,7 +4076,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER).Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4045,7 +4091,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = ExtensionIcon; CODE_SIGN_ENTITLEMENTS = Extension/Extension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = 3L4U93393E; INFOPLIST_FILE = Extension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -4054,7 +4100,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.7.3; + MARKETING_VERSION = 2.7.4; PRODUCT_BUNDLE_IDENTIFIER = "$(PARENT_PRODUCT_BUNDLE_IDENTIFIER).Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Provider/gl.lproj/InfoPlist.strings b/Provider/gl.lproj/InfoPlist.strings new file mode 100644 index 0000000..545d6d9 --- /dev/null +++ b/Provider/gl.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +"NSCameraUsageDescription" = "Escanear código QR"; +"NSFaceIDUsageDescription" = "Desbloquear a aplicación"; +"NSPhotoLibraryAddUsageDescription" = "Compartir código QR"; diff --git a/Provider/pl.lproj/InfoPlist.strings b/Provider/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000..de247f9 --- /dev/null +++ b/Provider/pl.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +"NSPhotoLibraryAddUsageDescription" = "Udostępnij kod QR"; +"NSCameraUsageDescription" = "Skanuj kodu QR"; +"NSFaceIDUsageDescription" = "Odblokuj aplikację"; diff --git a/README.md b/README.md index 1cefe06..59d7d97 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ You can install beta builds by joining the [TestFlight](https://testflight.apple - Catalan: [Maite Guix](https://hosted.weblate.org/user/maite.guix) - Swedish: [Anders Johansson](https://github.com/tellustheguru) - Ukrainian: [Markevych Dmytro](https://github.com/Hotr1pak) +- Galician: [Miguel A. Bouzada](https://github.com/mbouzada) +- Polish: [Radosław Rudner](https://hosted.weblate.org/user/rudass) Everybody is welcome to contribute translations via [Weblate](https://hosted.weblate.org/engage/nextcloud-passwords-ios)! diff --git a/Shared/Views/Components/WebView.swift b/Shared/Views/Components/WebView.swift index 4008d40..e5dcc15 100644 --- a/Shared/Views/Components/WebView.swift +++ b/Shared/Views/Components/WebView.swift @@ -31,6 +31,7 @@ struct WebView: UIViewRepresentable { } let webView = BottomlessWKWebView(frame: .zero, configuration: configuration) webView.navigationDelegate = context.coordinator + webView.uiDelegate = context.coordinator webView.isOpaque = false webView.customUserAgent = userAgent #if DEBUG @@ -55,7 +56,7 @@ struct WebView: UIViewRepresentable { extension WebView { - final class Coordinator: NSObject, WKNavigationDelegate { + final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { private let webView: WebView fileprivate(set) var latestRequest: URLRequest? @@ -73,6 +74,13 @@ extension WebView { webView.request = navigationAction.request } + func webView(_ webView: WKWebView, createWebViewWith _: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures _: WKWindowFeatures) -> WKWebView? { + if navigationAction.targetFrame == nil { + webView.load(navigationAction.request) + } + return nil + } + func webView(_: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { guard let trust = challenge.protectionSpace.serverTrust, await webView.checkTrust?(trust) == true else { diff --git a/Shared/gl.lproj/Localizable.strings b/Shared/gl.lproj/Localizable.strings new file mode 100644 index 0000000..0c27ad7 --- /dev/null +++ b/Shared/gl.lproj/Localizable.strings @@ -0,0 +1,161 @@ +"_addTags" = "Engadir etiquetas"; +"_addManually" = "Engadir manualmente"; +"_algorithm" = "Algoritmo"; +"_all" = "Todo"; +"_anErrorOccurred" = "Produciuse un erro…"; +"_cancel" = "Cancelar"; +"_deleteFolderErrorMessage" = "Produciuse un erro ao eliminar o cartafol. Elimine o cartafol de novo."; +"_edit" = "Editar"; +"_deletePasswordErrorMessage" = "Produciuse un erro ao eliminar o contrasinal. Elimine o contrasinal de novo."; +"_deleteTagErrorMessage" = "Produciuse un erro ao eliminar a etiqueta. Elimine a etiqueta de novo."; +"_editTags" = "Editar etiquetas"; +"_digits" = "Díxitos"; +"_e2ePassword" = "Contrasinal E2E"; +"_editPasswordErrorMessage" = "Produciuse un erro ao editar o contrasinal. Garde o contrasinal de novo."; +"_editTagErrorMessage" = "Produciuse un erro ao editar a etiqueta. Garde a etiqueta de novo."; +"_email" = "Correo-e"; +"_extractOtpErrorMessage" = "Produciuse un erro ao buscar un código QR na imaxe seleccionada."; +"_favorite" = "Favorito"; +"_generatePassword" = "Xerar contrasinal"; +"_file" = "Ficheiro"; +"_folder" = "Cartafol"; +"_folders" = "Cartafoles"; +"_giveATip" = "Dar unha adealla"; +"_highFive" = "High Five! 👏"; +"_id" = "ID"; +"_incorrectPassword" = "Contrasinal incorrecto"; +"_clearStoredE2EPassword" = "Limpar o contrasinal E2E almacenado"; +"_clientSide" = "Do lado do cliente"; +"_color" = "Cor"; +"_aboutOtps" = "Sobre os contrasinais dun só uso"; +"_addCustomField" = "Engadir campo personalizado"; +"_addOtp" = "Engadir contrasinal dun só uso"; +"_appDeauthorized" = "Aplicación desautorizada"; +"_appDeauthorizedMessage" = "Por mor de moitos intentos de acceso fallados, esta aplicación foi desautorizada polo servidor. Ténteo de novo máis tarde."; +"_automaticallyGeneratePasswords" = "Xerar contrasinais automaticamente"; +"_configured" = "Configurado"; +"_confirm" = "Confirmar"; +"_confirmAction" = "Confirmar a acción"; +"_connect" = "Conectar"; +"_connectingToNextcloudInstanceAt(url)" = "Conectando coa instancia de Nextcloud en\n%@"; +"_connectToServer" = "Conectar co servidor"; +"_copyOtp" = "Copiar o contrasinal dun só uso"; +"_copyPassword" = "Copiar o contrasinal"; +"_copyUsername" = "Copiar o nome de usuario"; +"_created" = "Creado"; +"_createFolder" = "Crear cartafol"; +"_createFolderErrorMessage" = "Produciuse un erro ao crear o cartafol. Garde o cartafol de novo."; +"_createPassword" = "Crear contrasinal"; +"_createPasswordErrorMessage" = "Produciuse un erro ao crear o contrasinal. Garde o contrasinal de novo."; +"_deletePassword" = "Eliminar contrasinal"; +"_deleteTag" = "Eliminar etiqueta"; +"_detectQrCodeInPicture" = "Detectar código QR na imaxe"; +"_discardChanges" = "Desbotar cambios"; +"_done" = "Feito"; +"_duplicates" = "Duplicados"; +"_duplicatesTrashMessage" = "Todos os duplicados deste contrasinal atópanse no lixo."; +"_dismiss" = "Desbotar"; +"_editFolderErrorMessage" = "Produciuse un erro ao editar o cartafol. Garde o cartafol de novo."; +"_editPassword" = "Editar contrasinal"; +"_encryptedOfflineStorage" = "Almacenamento cifrado sen conexión"; +"_encryption" = "Cifrado"; +"_error" = "Erro"; +"_exportAsQrCode" = "Exportar como código QR"; +"_favorites" = "Favoritos"; +"_hash" = "Resumo (criptográfico)"; +"_incorrectPasswordMessage" = "Ténteo de novo."; +"_integration" = "Integración"; +"_invalidCertificate" = "Certificado non válido"; +"_joinTestFlightBeta" = "Únase a TestFlight Beta"; +"_logIn" = "Acceder"; +"_low" = "Baixa"; +"_metadata" = "Metadatos"; +"_moreOptions" = "Máis opcións"; +"_move" = "Mover"; +"_name" = "Nome"; +"_noConnectionMessage" = "Non foi posíbel conectar coa instancia de contrasinais de Nextcloud."; +"_otp" = "Contrasinal dun só uso"; +"_openUrl" = "Abrir o URL"; +"_password" = "Contrasinal"; +"_passwordStatusUnknownMessage" = "Aínda non se comprobou a súa seguranza deste contrasinal ou a opción de almacenar resumos para as comprobacións de seguranza está desactivada."; +"_qrCaptureErrorMessage" = "Produciuse un erro ao iniciar a cámara."; +"_reject" = "Rexeitar"; +"_rootFolder" = "Cartafol raíz"; +"_scanQrCode" = "Escanear o código QR"; +"_selectColor" = "Seleccionar a cor"; +"_serverAddressFieldMessage" = "Requírese unha conexión HTTPS."; +"_sourceCode" = "Código fonte"; +"_settings" = "Axustes"; +"_storePassword" = "Almacenar"; +"_text" = "Texto"; +"_tipDeferred" = "Adealla aprazada"; +"_tipDeferredMessage" = "A súa adealla está a agardar aprobación."; +"_tipFailed" = "Adealla fallada"; +"_tryAgain" = "Ténteo de novo"; +"_tipReceived" = "Adealla recibida"; +"_tipReceivedMessage" = "Grazas por apoiar este proxecto coa súa adealla!"; +"_ultra" = "Máxima"; +"_updated" = "Actualizado"; +"_url" = "URL"; +"_username" = "Nome de usuario"; +"_valueCopied" = "Valor copiado"; +"_counter" = "Contador"; +"_version" = "Versión"; +"_counterBased" = "En función do contador"; +"_create" = "Crear"; +"_timeBased" = "En función do tempo"; +"_serverSide" = "Do lado do servidor"; +"_deleteOtp" = "Eliminar o contrasinal dun só uso"; +"_invalidCertificateMessage(hash)" = "O certificado do servidor non é válido. Non é posíbel garantir unha conexión segura. Debe seguir a empregarse o certificado?\n\nSHA-256 do certificado:\n%@"; +"_about" = "Sobre"; +"_createTag" = "Crear etiqueta"; +"_createTagErrorMessage" = "Produciuse un erro ao crear a etiqueta. Garde a etiqueta de novo."; +"_credentials" = "Credenciais"; +"_customFields" = "Campos personalizados"; +"_logOut" = "Saír"; +"_decryptFolderErrorMessage" = "Produciuse un erro ao descifrar o cartafol."; +"_decryptTagErrorMessage" = "Produciuse un erro ao descifrar a etiqueta."; +"_decryptPasswordErrorMessage" = "Produciuse un erro ao descifrar o contrasinal."; +"_deletedPasswordMessage" = "Este contrasinal foi eliminado."; +"_deleteFolder" = "Eliminar cartafol"; +"_delete" = "Eliminar"; +"_aboutOtpsMessage" = "Esta aplicación admite contrasinais dun só uso só por comodidade. Almacenar o seu contrasinal dun só uso xunto co contrasinal da súa conta elimina algúns dos beneficios de seguranza da autenticación multifactor. Activar un segundo factor é mellor que non usar ningún, mais unha aplicación independente dun contrasinal dun só uso será máis segura. NON garde contrasinais dun só uso para a súa conta de Nextcloud ou contas críticas, coma o correo-e ou contas de banca en liña. Os contrasinais dun só uso sincronizaranse coa súa instancia de Contrasinais de Nextcloud, mais actualmente só son compatíbeis coa aplicación iOS (v2.6 ou posterior)."; +"_accept" = "Acepto"; +"_account" = "Conta"; +"_managedServerUrlErrorMessage" = "O enderezo do servidor Nextcloud foi preconfigurado, mais a aplicación non foi quen de conectarse á instancia de Nextcloud. Póñase en contacto coa administración da instancia."; +"_medium" = "Media"; +"_options" = "Opcións"; +"_otps" = "Contrasinais dun só uso"; +"_passwords" = "Contrasinais"; +"_passwordServiceErrorMessage" = "Produciuse un erro ao xerar o contrasinal."; +"_periodSeconds" = "Lapso (segundos)"; +"_secret" = "Segredo"; +"_security" = "Seguridade"; +"_select" = "Seleccionar"; +"_service" = "Servizo"; +"_shareQrCode" = "Compartir o código QR"; +"_specialCharacters" = "Caracteres especiais"; +"_strength" = "Fortaleza"; +"_supportThisProject" = "Apoiar este proxecto"; +"_storePasswordMessage" = "Se esta opción está activada, o contrasinal E2E almacénase cifrado no chaveiro do seu dispositivo."; +"_tag" = "Etiqueta"; +"_suggestions" = "Suxestións"; +"_tags" = "Etiquetas"; +"_universalClipboard" = "Portapapeis universal"; +"_tipFailedMessage" = "Produciuse un erro ao procesar a súa adealla."; +"_type" = "Tipo"; +"_unlockApp" = "Desbloquear a aplicación"; +"_passwordStatusBreachedMessage" = "Este contrasinal non é seguro. Foi atopado nunha base de datos de contrasinais comprometidos."; +"_nextcloudServerAddress" = "Enderezo do servidor de Nextcloud"; +"_notes" = "Notas"; +"_noConnection" = "Non hai conexión"; +"_nothingToSeeHere" = "Non hai nada que ver aquí…"; +"_numbers" = "Números"; +"_offline" = "Sen conexión"; +"_passwordStatusDuplicateMessage" = "Este contrasinal está a ser usado máis dunha vez."; +"_passwordStatusGoodMessage" = "Este contrasinal é seguro. Non foi comprometido e non infrinxe as regras de seguranza definidas."; +"_providerInstructionsMessage" = "Esta aplicación admite a enchedura automática de contrasinais. A función ten que estar activada na aplicación de Axustes de iOS:\n1. Abra a aplicación Axustes\n2. Toque en Contrasinais\n3. Toque en Enchedura automática de contrasinais\n4. Active a Enchedura automática de contrasinais\n5. Active esta aplicación"; +"_passwordStatusOutdatedMessage" = "Este contrasinal acadou a duración límite."; +"_shareOtpWarningMessage" = "Este código QR contén datos moi sensíbeis sobre o contrasinal dun só uso"; +"_supportThisProjectMessage" = "A versión TestFlight desta aplicación está instalada. As compras realizanse só a efecto de probas e non van ser facturadas."; +"_thanksMessage" = "Grazas a toda a comunidade Nextcloud e a todos os implicados por facer posíbel un ecosistema tan incrible. Agardo poder achegar algo con esta aplicación."; diff --git a/Shared/pl.lproj/Localizable.strings b/Shared/pl.lproj/Localizable.strings new file mode 100644 index 0000000..b414db6 --- /dev/null +++ b/Shared/pl.lproj/Localizable.strings @@ -0,0 +1,161 @@ +"_algorithm" = "Algorytm"; +"_aboutOtps" = "O hasłach jednorazowych"; +"_addTags" = "Dodaj tagi"; +"_about" = "O"; +"_addCustomField" = "Dodaj niestandardowe pole"; +"_addOtp" = "Dodaj hasło jednorazowe"; +"_clearStoredE2EPassword" = "Wyczyść zapisane hasło E2E"; +"_created" = "Utworzono"; +"_deletedPasswordMessage" = "Hasło zostało usunięte."; +"_detectQrCodeInPicture" = "Wykryj kod QR ze zdjęcia"; +"_discardChanges" = "Odrzuć zmiany"; +"_integration" = "Integracja"; +"_highFive" = "High Five! 👏"; +"_folder" = "Folder"; +"_id" = "ID"; +"_incorrectPassword" = "Nieprawidłowe hasło"; +"_giveATip" = "Daj napiwek"; +"_hash" = "Hash"; +"_generatePassword" = "Generuj hasło"; +"_folders" = "Foldery"; +"_joinTestFlightBeta" = "Dołącz do bety TestFlight"; +"_invalidCertificate" = "Nieprawidłowy certyfikat"; +"_moreOptions" = "Więcej opcji"; +"_low" = "Niska"; +"_noConnection" = "Brak połączenia"; +"_passwords" = "Hasła"; +"_password" = "Hasło"; +"_passwordStatusDuplicateMessage" = "To hasło jest używane więcej niż jeden raz."; +"_providerInstructionsMessage" = "Ta aplikacja obsługuje autouzupełnianie haseł. Funkcję tę należy włączyć w aplikacji Ustawienia systemu iOS:\n1. Otwórz aplikację Ustawienia\n2. Stuknij opcję Hasła\n3. Stuknij opcję Autouzupełnianie haseł\n4. Włącz autouzupełnianie haseł\n5. Włącz tę aplikację"; +"_qrCaptureErrorMessage" = "Wystąpił błąd podczas włączania aparatu."; +"_suggestions" = "Sugestie"; +"_storePasswordMessage" = "Jeśli ta opcja jest włączona, hasło end-to-end jest przechowywane w postaci zaszyfrowanej w pęku kluczy urządzenia."; +"_updated" = "Zaktualizowany"; +"_unlockApp" = "Odblokuj aplikację"; +"_valueCopied" = "Zawartość skopiowana"; +"_username" = "Nazwa użytkownika"; +"_version" = "Wersja"; +"_reject" = "Odrzuć"; +"_tags" = "Tagi"; +"_name" = "Nazwa"; +"_duplicates" = "Duplikaty"; +"_security" = "Bezpieczeństwo"; +"_createPasswordErrorMessage" = "Podczas tworzenia hasła wystąpił błąd. Zapisz hasło ponownie."; +"_rootFolder" = "Folder główny"; +"_appDeauthorized" = "Aplikacja Nieautoryzowana"; +"_customFields" = "Niestandardowe pola"; +"_createPassword" = "Utwórz hasło"; +"_scanQrCode" = "Skanuj kod QR"; +"_timeBased" = "Oparte na czasie"; +"_editPassword" = "Edytuj hasło"; +"_options" = "Opcje"; +"_select" = "Wybierz"; +"_createFolderErrorMessage" = "Wystąpił błąd podczas tworzenia folderu. Zapisz folder ponownie."; +"_extractOtpErrorMessage" = "Wystąpił błąd podczas wyszukiwania kodu QR na wybranym obrazie."; +"_thanksMessage" = "Dziękuję całej społeczności Nextcloud i wszystkim zaangażowanym za umożliwienie tak niesamowitego ekosystemu. Mam nadzieję, że mogę coś wnieść dzięki tej aplikacji."; +"_counterBased" = "Oparty na liczniku"; +"_confirm" = "Potwierdź"; +"_shareOtpWarningMessage" = "Ten kod QR zawiera wysoce poufne dane dotyczące hasła jednorazowego"; +"_strength" = "Siła"; +"_offline" = "Offline"; +"_metadata" = "Metadane"; +"_anErrorOccurred" = "Wystąpił błąd…"; +"_numbers" = "Liczby"; +"_credentials" = "Dane logowania"; +"_tryAgain" = "Spróbuj ponownie"; +"_otps" = "Jednorazowe hasła"; +"_connectToServer" = "Połącz z Serwerem"; +"_done" = "Gotowe"; +"_type" = "Typ"; +"_automaticallyGeneratePasswords" = "Automatyczne generowanie haseł"; +"_editFolderErrorMessage" = "Wystąpił błąd podczas edycji folderu. Zapisz folder ponownie."; +"_nextcloudServerAddress" = "Adres serwera Nextcloud"; +"_exportAsQrCode" = "Eksportuj jako kod QR"; +"_edit" = "Edytuj"; +"_supportThisProject" = "Wspieraj ten projekt"; +"_nothingToSeeHere" = "Nie ma tu nic do zobaczenia…"; +"_duplicatesTrashMessage" = "Wszystkie duplikaty tego hasła znajdują się w koszu."; +"_invalidCertificateMessage(hash)" = "Certyfikat serwera jest nieprawidłowy. Nie można zagwarantować bezpiecznego połączenia. Czy certyfikat powinien być nadal używany?\n\nSHA-256 certyfikatu:\n%@"; +"_tipFailed" = "Napiwek nie powiódł się"; +"_settings" = "Ustawienia"; +"_error" = "Bład"; +"_tipReceivedMessage" = "Dziękujemy za wsparcie tego projektu swoim napiwkiem!"; +"_decryptTagErrorMessage" = "Wystąpił błąd podczas odszyfrowywania tagu."; +"_editTagErrorMessage" = "Wystąpił błąd podczas edycji tagu. Zapisz tag ponownie."; +"_openUrl" = "Otwórz adres URL"; +"_connectingToNextcloudInstanceAt(url)" = "Połączenie z instancją Nextcloud pod adresem\n%@"; +"_color" = "Kolor"; +"_universalClipboard" = "Schowek uniwersalny"; +"_dismiss" = "Odrzuć"; +"_passwordStatusBreachedMessage" = "To hasło nie jest bezpieczne. Zostało ono znalezione w bazie danych naruszonych haseł."; +"_noConnectionMessage" = "Nie można połączyć się z instancją Nextcloud Passwords."; +"_copyOtp" = "Skopiuj jednorazowe hasło"; +"_medium" = "Średnia"; +"_specialCharacters" = "Znaki specjalne"; +"_aboutOtpsMessage" = "Ta aplikacja obsługuje hasła jednorazowe wyłącznie dla wygody. Przechowywanie hasła jednorazowego wraz z hasłem do konta eliminuje niektóre korzyści związane z bezpieczeństwem uwierzytelniania wieloskładnikowego. Włączenie drugiego czynnika jest lepsze niż nieużywanie go w ogóle, ale samodzielna aplikacja do haseł jednorazowych będzie bezpieczniejsza. NIE przechowuj jednorazowych haseł do konta Nextcloud lub krytycznych kont, takich jak poczta e-mail lub konta bankowości internetowej. Hasła jednorazowe będą synchronizowane przez instancję Nextcloud Passwords, ale obecnie są obsługiwane tylko przez aplikację na iOS (wersja 2.6 lub nowsza)."; +"_e2ePassword" = "hasło end-to-end"; +"_favorite" = "Ulubione"; +"_counter" = "Licznik"; +"_url" = "Adres URL"; +"_encryptedOfflineStorage" = "Szyfrowane przechowywanie offline"; +"_encryption" = "Szyfrowanie"; +"_shareQrCode" = "Udostępnij kod QR"; +"_tag" = "Tag"; +"_email" = "Email"; +"_createTagErrorMessage" = "Wystąpił błąd podczas tworzenia tagu. Zapisz tag ponownie."; +"_copyPassword" = "Skopiuj hasło"; +"_tipReceived" = "Napiwek otrzymany"; +"_tipFailedMessage" = "Wystąpił błąd podczas przetwarzania napiwku."; +"_service" = "Usługa"; +"_managedServerUrlErrorMessage" = "Adres serwera Nextcloud został wstępnie skonfigurowany, ale aplikacja nie może połączyć się z instancją Nextcloud. Prosimy o kontakt z administratorem."; +"_tipDeferred" = "Napiwek odrzucony"; +"_configured" = "Skonfigurowany"; +"_all" = "Wszystkie"; +"_supportThisProjectMessage" = "Zainstalowana jest wersja TestFlight tej aplikacji. Wszelkie dokonane zakupy służą wyłącznie do celów testowych i nie będą rozliczane."; +"_decryptPasswordErrorMessage" = "Wystąpił błąd podczas odszyfrowywania hasła."; +"_confirmAction" = "Potwierdź działanie"; +"_passwordServiceErrorMessage" = "Wystąpił błąd podczas generowania hasła."; +"_editTags" = "Edytuj tagi"; +"_deleteTagErrorMessage" = "Wystąpił błąd podczas usuwania tagu. Usuń tag ponownie."; +"_serverSide" = "Strona serwera"; +"_selectColor" = "Wybierz kolor"; +"_deleteOtp" = "Usuń hasło jednorazowe"; +"_addManually" = "Dodaj ręcznie"; +"_secret" = "Sekret"; +"_passwordStatusGoodMessage" = "To hasło jest bezpieczne w użyciu. Nie zostało złamane i nie narusza zasad zdefiniowanych przez użytkownika."; +"_appDeauthorizedMessage" = "Z powodu zbyt wielu nieudanych prób logowania ta aplikacja została zablokowana przez serwer. Spróbuj ponownie później."; +"_move" = "Przenieś"; +"_passwordStatusOutdatedMessage" = "To hasło osiągnęło maksymalny wiek."; +"_serverAddressFieldMessage" = "Wymagane jest połączenie HTTPS."; +"_editPasswordErrorMessage" = "Wystąpił błąd podczas edycji hasła. Zapisz hasło ponownie."; +"_file" = "Plik"; +"_passwordStatusUnknownMessage" = "To hasło nie zostało jeszcze sprawdzone pod kątem bezpieczeństwa lub opcja przechowywania hashy w celu sprawdzenia bezpieczeństwa jest wyłączona."; +"_text" = "Tekst"; +"_periodSeconds" = "Okres (w sekundach)"; +"_tipDeferredMessage" = "Twój napiwek czeka teraz na zatwierdzenie."; +"_deletePassword" = "Usuń hasło"; +"_createFolder" = "Utwórz Folder"; +"_connect" = "Połącz"; +"_create" = "Utwórz"; +"_digits" = "Cyfry"; +"_deleteFolderErrorMessage" = "Wystąpił błąd podczas usuwania folderu. Usuń folder ponownie."; +"_notes" = "Notatki"; +"_copyUsername" = "Skopiuj nazwę użytkownika"; +"_deleteFolder" = "Usuń folder"; +"_decryptFolderErrorMessage" = "Wystąpił błąd podczas odszyfrowywania folderu."; +"_account" = "Konto"; +"_logIn" = "Zaloguj się"; +"_deleteTag" = "Usuń tag"; +"_deletePasswordErrorMessage" = "Wystąpił błąd podczas usuwania hasła. Usuń hasło ponownie."; +"_createTag" = "Utwórz Tag"; +"_accept" = "Akceptuj"; +"_storePassword" = "Zapisz hasło"; +"_delete" = "Usuń"; +"_favorites" = "Ulubione"; +"_sourceCode" = "Kod źródłowy"; +"_otp" = "Hasło jednorazowe"; +"_logOut" = "Wyloguj się"; +"_incorrectPasswordMessage" = "Spróbuj ponownie."; +"_clientSide" = "Po stronie klienta"; +"_cancel" = "Anuluj"; +"_ultra" = "Ultra"; diff --git a/Tests/Extensions/Conformance/Array+Initializable.swift b/Tests/Extensions/Conformance/Array+Initializable.swift new file mode 100644 index 0000000..db8e89b --- /dev/null +++ b/Tests/Extensions/Conformance/Array+Initializable.swift @@ -0,0 +1 @@ +extension Array: Initializable {} diff --git a/Tests/Matchers/Logging/BeAccessed.swift b/Tests/Matchers/Logging/BeAccessed.swift index af8a7e8..2360521 100644 --- a/Tests/Matchers/Logging/BeAccessed.swift +++ b/Tests/Matchers/Logging/BeAccessed.swift @@ -3,29 +3,15 @@ import Nimble -enum AccessCount { - - case anyNumberOfTimes - case once - case twice - case thrice - case aSpecifiedAmount(Int) - - var rawValue: Int { - switch self { - case .anyNumberOfTimes: - return 0 - case .once: - return 1 - case .twice: - return 2 - case .thrice: - return 3 - case let .aSpecifiedAmount(count): - return count - } +func beAccessed(_ accessCount: AccessCount = .anyNumberOfTimes, on expectedAccess: String? = nil) -> Matcher { + .init { expression in + let result = try beAccessed(accessCount, on: expectedAccess).satisfies( + .init(expression: { + try expression.evaluate().map(StaticPropertyAccessLoggerSnapshot.init) + }, location: expression.location, isClosure: expression.isClosure) + ) + return .init(status: result.status, message: result.message) } - } @@ -62,3 +48,40 @@ func beAccessed(_ accessCount: AccessCount = .anyNumbe return .init(status: .matches, message: message) } } + + +private class StaticPropertyAccessLoggerSnapshot: PropertyAccessLogging { + + var propertyAccessLog: Log + + init(_ functionCallLogger: L.Type) { + propertyAccessLog = functionCallLogger.propertyAccessLog + } + +} + + +enum AccessCount { + + case anyNumberOfTimes + case once + case twice + case thrice + case aSpecifiedAmount(Int) + + var rawValue: Int { + switch self { + case .anyNumberOfTimes: + return 0 + case .once: + return 1 + case .twice: + return 2 + case .thrice: + return 3 + case let .aSpecifiedAmount(count): + return count + } + } + +} diff --git a/Tests/Matchers/Logging/BeCalled.swift b/Tests/Matchers/Logging/BeCalled.swift index 0f15c6b..5b31c63 100644 --- a/Tests/Matchers/Logging/BeCalled.swift +++ b/Tests/Matchers/Logging/BeCalled.swift @@ -3,32 +3,6 @@ import Nimble -enum CallCount { - - case anyNumberOfTimes - case once - case twice - case thrice - case aSpecifiedAmount(Int) - - var rawValue: Int { - switch self { - case .anyNumberOfTimes: - return 0 - case .once: - return 1 - case .twice: - return 2 - case .thrice: - return 3 - case let .aSpecifiedAmount(count): - return count - } - } - -} - - func beCalled(_ callCount: CallCount = .anyNumberOfTimes, on expectedCall: String? = nil) -> Matcher { beCalled(callCount, on: expectedCall, atCallIndex: nil) } @@ -40,6 +14,34 @@ func beCalled(_ callCount: CallCount = .anyNumberOfTimes func beCalled(_ callCount: CallCount = .anyNumberOfTimes, on expectedCall: String? = nil, withParameters expectedParameters: any Equatable..., atCallIndex parameterCallIndex: Int? = nil) -> Matcher { + beCalled(callCount, on: expectedCall, withParameters: expectedParameters, atCallIndex: parameterCallIndex) +} + + +func beCalled(_ callCount: CallCount = .anyNumberOfTimes, on expectedCall: String? = nil) -> Matcher { + beCalled(callCount, on: expectedCall, atCallIndex: nil) +} + + +func beCalled(_ callCount: CallCount = .anyNumberOfTimes, on expectedCall: String? = nil, withParameter expectedParameter: any Equatable, atCallIndex parameterCallIndex: Int? = nil) -> Matcher { + beCalled(callCount, on: expectedCall, withParameters: expectedParameter, atCallIndex: parameterCallIndex) +} + + +func beCalled(_ callCount: CallCount = .anyNumberOfTimes, on expectedCall: String? = nil, withParameters expectedParameters: any Equatable..., atCallIndex parameterCallIndex: Int? = nil) -> Matcher { + .init { expression in + let result = try beCalled(callCount, on: expectedCall, withParameters: expectedParameters, atCallIndex: parameterCallIndex).satisfies( + .init(expression: { + try expression.evaluate().map(StaticFunctionCallLoggerSnapshot.init) + }, location: expression.location, isClosure: expression.isClosure) + ) + return .init(status: result.status, message: result.message) + } + +} + + +private func beCalled(_ callCount: CallCount, on expectedCall: String?, withParameters expectedParameters: [any Equatable], atCallIndex parameterCallIndex: Int?) -> Matcher { .init { expression in var message: ExpectationMessage if let expectedCall { @@ -90,6 +92,43 @@ func beCalled(_ callCount: CallCount = .anyNumberOfTimes } +private class StaticFunctionCallLoggerSnapshot: FunctionCallLogging { + + var functionCallLog: Log + + init(_ functionCallLogger: L.Type) { + functionCallLog = functionCallLogger.functionCallLog + } + +} + + +enum CallCount { + + case anyNumberOfTimes + case once + case twice + case thrice + case aSpecifiedAmount(Int) + + var rawValue: Int { + switch self { + case .anyNumberOfTimes: + return 0 + case .once: + return 1 + case .twice: + return 2 + case .thrice: + return 3 + case let .aSpecifiedAmount(count): + return count + } + } + +} + + private func compare(_ equatables: [any Equatable], to expected: [any Equatable]) -> Bool { guard equatables.count == expected.count else { return false diff --git a/Tests/Mocks/Seams/ProductMock.swift b/Tests/Mocks/Seams/ProductMock.swift index 64e288c..c394a7a 100644 --- a/Tests/Mocks/Seams/ProductMock.swift +++ b/Tests/Mocks/Seams/ProductMock.swift @@ -4,8 +4,10 @@ import Foundation final class ProductMock: Product, Mock, PropertyAccessLogging, FunctionCallLogging { - static func products(for identifiers: Identifiers) async throws -> [ProductMock] where Identifiers: Collection, Identifiers.Element == String { - [] // TODO: mock & log + static var _products: Result<[ProductMock], any Error> = .success([]) // swiftlint:disable:this identifier_name + static func products(for identifiers: Identifiers) async throws -> [ProductMock] where Identifiers.Element == String { + logFunctionCall(parameters: Array(identifiers)) + return try _products.get() } var _id = String.random() // swiftlint:disable:this identifier_name diff --git a/Tests/Utilities/Architecture/Associating.swift b/Tests/Utilities/Architecture/Associating.swift new file mode 100644 index 0000000..fe89260 --- /dev/null +++ b/Tests/Utilities/Architecture/Associating.swift @@ -0,0 +1,51 @@ +import ObjectiveC.runtime + + +private class AssociationType {} + + +protocol Associating: AnyObject {} + + +extension Associating { + + static func getAssociated(with source: Any = Self.self) -> Value { + guard let value = objc_getAssociatedObject(source, associationKey(Value.self)) as? Value else { + let value = Value() + setAssociated(value, with: source) + return value + } + return value + } + + static func setAssociated(_ value: Value, with source: Any = Self.self) { + objc_setAssociatedObject(source, associationKey(Value.self), value, .OBJC_ASSOCIATION_RETAIN) + } + + private static func associationKey(_ valueType: Value.Type) -> UnsafeRawPointer { + .init( + bitPattern: UInt( + bitPattern: .init( + AssociationType.self + ) + ) + )! + } + + func getAssociated(with source: Any) -> Value { + Self.getAssociated(with: source) + } + + func getAssociated() -> Value { + getAssociated(with: self) + } + + func setAssociated(_ value: Value, with source: Any) { + Self.setAssociated(value, with: source) + } + + func setAssociated(_ value: Value) { + setAssociated(value, with: self) + } + +} diff --git a/Tests/Utilities/Architecture/Initializable.swift b/Tests/Utilities/Architecture/Initializable.swift new file mode 100644 index 0000000..45d7f5a --- /dev/null +++ b/Tests/Utilities/Architecture/Initializable.swift @@ -0,0 +1,5 @@ +protocol Initializable { + + init() + +} diff --git a/Tests/Utilities/Mocking/FunctionCallLogging.swift b/Tests/Utilities/Mocking/FunctionCallLogging.swift index 589102e..5e1d549 100644 --- a/Tests/Utilities/Mocking/FunctionCallLogging.swift +++ b/Tests/Utilities/Mocking/FunctionCallLogging.swift @@ -1,38 +1,32 @@ -import ObjectiveC.runtime - - -protocol FunctionCallLogging: AnyObject { +protocol FunctionCallLogging: AnyObject, Associating { typealias Log = [(functionName: String, parameters: [any Equatable])] + static var functionCallLog: Log { get set } var functionCallLog: Log { get set } } -private var kFunctionCallLog = malloc(1) - - extension FunctionCallLogging { + static var functionCallLog: Log { + get { getAssociated() } + set { setAssociated(newValue) } + } + var functionCallLog: Log { - get { - guard let functionCallLog = objc_getAssociatedObject(self, &kFunctionCallLog) as? Log else { - let functionCallLog = Log() - self.functionCallLog = functionCallLog - return functionCallLog - } - return functionCallLog - } - set { - objc_setAssociatedObject(self, &kFunctionCallLog, newValue, .OBJC_ASSOCIATION_RETAIN) - } + get { getAssociated() } + set { setAssociated(newValue) } } -} - - -extension FunctionCallLogging { + static func functionCallLog(of functionName: String) -> Log { + functionCallLog.filter { $0.functionName == functionName } + } + + static func logFunctionCall(of functionName: String = #function, parameters: any Equatable...) { + functionCallLog.append((functionName: functionName, parameters: parameters)) + } func functionCallLog(of functionName: String) -> Log { functionCallLog.filter { $0.functionName == functionName } diff --git a/Tests/Utilities/Mocking/PropertyAccessLogging.swift b/Tests/Utilities/Mocking/PropertyAccessLogging.swift index d6e96fd..4f8b7fb 100644 --- a/Tests/Utilities/Mocking/PropertyAccessLogging.swift +++ b/Tests/Utilities/Mocking/PropertyAccessLogging.swift @@ -1,38 +1,32 @@ -import ObjectiveC.runtime - - -protocol PropertyAccessLogging: AnyObject { +protocol PropertyAccessLogging: AnyObject, Associating { typealias Log = [String] + static var propertyAccessLog: Log { get set } var propertyAccessLog: Log { get set } } -private var kPropertyAccessLog = malloc(1) - - extension PropertyAccessLogging { + static var propertyAccessLog: Log { + get { getAssociated() } + set { setAssociated(newValue) } + } + var propertyAccessLog: Log { - get { - guard let propertyAccessLog = objc_getAssociatedObject(self, &kPropertyAccessLog) as? Log else { - let propertyAccessLog = Log() - self.propertyAccessLog = propertyAccessLog - return propertyAccessLog - } - return propertyAccessLog - } - set { - objc_setAssociatedObject(self, &kPropertyAccessLog, newValue, .OBJC_ASSOCIATION_RETAIN) - } + get { getAssociated() } + set { setAssociated(newValue) } } -} - - -extension PropertyAccessLogging { + static func propertyAccessLog(of propertyName: String) -> Log { + propertyAccessLog.filter { $0 == propertyName } + } + + static func logPropertyAccess(of propertyName: String = #function) { + propertyAccessLog.append(propertyName) + } func propertyAccessLog(of propertyName: String) -> Log { propertyAccessLog.filter { $0 == propertyName } diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt index dd49b0c..725696e 100644 --- a/fastlane/metadata/de-DE/release_notes.txt +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -1,3 +1,3 @@ -• Ukrainische Übersetzung (Danke Markevych Dmytro!) -• Weitere Teile der App auf neue Architektur aktualisiert -• Fehlerbehebungen und Optimierungen +• Galicische Übersetzung (Danke Miguel A. Bouzada!) +• Polnische Übersetzung (Danke Radosław Rudner!) +• Optimierungen diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index e134698..d640be0 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,3 +1,3 @@ -• Ukrainian translation (Thanks Markevych Dmytro!) -• Upgraded more parts of the app to new architecture -• Bugfixes and optimizations +• Galician translation (Thanks Miguel A. Bouzada!) +• Polish translation (Thanks Radosław Rudner!) +• Optimizations