Switch to generated localized string symbols (#607)

* Switch to string symbols

* Names

* Cleanup packages

* Cleanup packages

* Remove namespace

* More cleanup

* Fix extra param.

* Use swiftbuild
This commit is contained in:
Max Goedjen 2025-08-17 22:26:13 -05:00 committed by GitHub
parent 83ecc15332
commit 9749cd6f3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 528 additions and 535 deletions

View File

@ -23,10 +23,7 @@ jobs:
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26_beta_5.app
- name: Test
run: |
pushd Sources/Packages
swift test
popd
run: swift build --build-system swiftbuild --package-path Sources/Packages
build:
# runs-on: macOS-latest
runs-on: macos-15

View File

@ -11,7 +11,4 @@ jobs:
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26_beta_5.app
- name: Test
run: |
pushd Sources/Packages
swift test
popd
run: swift build --build-system swiftbuild --package-path Sources/Packages

View File

@ -5,6 +5,7 @@ import PackageDescription
let package = Package(
name: "SecretivePackages",
defaultLocalization: "en",
platforms: [
.macOS(.v14)
],
@ -34,6 +35,7 @@ let package = Package(
.target(
name: "SecretKit",
dependencies: [],
resources: [localization],
swiftSettings: swiftSettings
),
.testTarget(
@ -44,16 +46,19 @@ let package = Package(
.target(
name: "SecureEnclaveSecretKit",
dependencies: ["SecretKit"],
resources: [localization],
swiftSettings: swiftSettings
),
.target(
name: "SmartCardSecretKit",
dependencies: ["SecretKit"],
resources: [localization],
swiftSettings: swiftSettings
),
.target(
name: "SecretAgentKit",
dependencies: ["SecretKit", "SecretAgentKitHeaders"],
resources: [localization],
swiftSettings: swiftSettings
),
.systemLibrary(
@ -66,6 +71,7 @@ let package = Package(
.target(
name: "Brief",
dependencies: [],
resources: [localization],
swiftSettings: swiftSettings
),
.testTarget(
@ -75,6 +81,10 @@ let package = Package(
]
)
var localization: Resource {
.process("../../Localizable.xcstrings")
}
var swiftSettings: [PackageDescription.SwiftSetting] {
[
.swiftLanguageMode(.v6),

View File

@ -0,0 +1 @@

View File

@ -50,16 +50,16 @@ extension SecureEnclave {
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
let newContext = LAContext()
newContext.touchIDAuthenticationAllowableReuseDuration = duration
newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.hour, .minute, .day]
if let durationString = formatter.string(from: duration) {
newContext.localizedReason = String(localized: "auth_context_persist_for_duration_\(secret.name)_\(durationString)")
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
} else {
newContext.localizedReason = String(localized: "auth_context_persist_for_duration_unknown_\(secret.name)")
newContext.localizedReason = String(localized: .authContextPersistForDurationUnknown(secretName: secret.name))
}
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
guard success else { return }

View File

@ -15,7 +15,7 @@ extension SecureEnclave {
CryptoKit.SecureEnclave.isAvailable
}
public let id = UUID()
public let name = String(localized: "secure_enclave")
public let name = String(localized: .secureEnclave)
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
/// Initializes a Store.
@ -105,10 +105,10 @@ extension SecureEnclave {
context = existing.context
} else {
let newContext = LAContext()
newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
context = newContext
}
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
let attributes = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
@ -138,8 +138,8 @@ extension SecureEnclave {
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
context.localizedReason = String(localized: .authContextRequestVerifyDescription(secretName: secret.name))
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let attributes = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
@ -240,7 +240,7 @@ extension SecureEnclave.Store {
nil)!
let wrapped: [SecureEnclave.Secret] = publicTyped.map {
let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret")
let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)
let id = $0[kSecAttrApplicationLabel] as! Data
let publicKeyRef = $0[kSecValueRef] as! SecKey
let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any]

View File

@ -9,7 +9,7 @@ extension SmartCard {
@MainActor @Observable fileprivate final class State {
var isAvailable = false
var name = String(localized: "smart_card")
var name = String(localized: .smartCard)
var secrets: [Secret] = []
let watcher = TKTokenWatcher()
var tokenID: String? = nil
@ -63,8 +63,8 @@ extension SmartCard {
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
guard let tokenID = await state.tokenID else { fatalError() }
let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let attributes = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
@ -162,7 +162,7 @@ extension SmartCard.Store {
@MainActor private func loadSecrets() {
guard let tokenID = state.tokenID else { return }
let fallbackName = String(localized: "smart_card")
let fallbackName = String(localized: .smartCard)
if let driverName = state.watcher.tokenInfo(forTokenID: tokenID)?.driverName {
state.name = driverName
} else {
@ -180,7 +180,7 @@ extension SmartCard.Store {
SecItemCopyMatching(attributes, &untyped)
guard let typed = untyped as? [[CFString: Any]] else { return }
let wrapped = typed.map {
let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret")
let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)
let tokenID = $0[kSecAttrApplicationLabel] as! Data
let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber)
let keySize = $0[kSecAttrKeySizeInBits] as! Int
@ -207,8 +207,8 @@ extension SmartCard.Store {
/// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged.
public func encrypt(data: Data, with secret: SecretType) throws -> Data {
let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
context.localizedReason = String(localized: .authContextRequestEncryptDescription(secretName: secret.name))
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let attributes = KeychainDictionary([
kSecAttrKeyType: secret.algorithm.secAttrKeyType,
kSecAttrKeySizeInBits: secret.keySize,
@ -236,8 +236,8 @@ extension SmartCard.Store {
public func decrypt(data: Data, with secret: SecretType) async throws -> Data {
guard let tokenID = await state.tokenID else { fatalError() }
let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
context.localizedReason = String(localized: .authContextRequestDecryptDescription(secretName: secret.name))
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let attributes = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,

View File

@ -10,8 +10,8 @@ final class Notifier: Sendable {
private let notificationDelegate = NotificationDelegate()
init() {
let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: String(localized: "update_notification_update_button"), options: [])
let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: String(localized: "update_notification_ignore_button"), options: [])
let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: String(localized: .updateNotificationUpdateButton), options: [])
let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: String(localized: .updateNotificationIgnoreButton), options: [])
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.criticalUpdateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
@ -22,7 +22,7 @@ final class Notifier: Sendable {
Measurement(value: 24, unit: UnitDuration.hours)
]
let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: "persist_authentication_decline_button"), options: [])
let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: .persistAuthenticationDeclineButton), options: [])
var allPersistenceActions = [doNotPersistAction]
let formatter = DateComponentsFormatter()
@ -41,7 +41,7 @@ final class Notifier: Sendable {
let persistAuthenticationCategory = UNNotificationCategory(identifier: Constants.persistAuthenticationCategoryIdentitifier, actions: allPersistenceActions, intentIdentifiers: [], options: [])
if persistAuthenticationCategory.responds(to: Selector(("actionsMenuTitle"))) {
persistAuthenticationCategory.setValue(String(localized: "persist_authentication_accept_button"), forKey: "_actionsMenuTitle")
persistAuthenticationCategory.setValue(String(localized: .persistAuthenticationAcceptButton), forKey: "_actionsMenuTitle")
}
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory, persistAuthenticationCategory])
UNUserNotificationCenter.current().delegate = notificationDelegate
@ -64,8 +64,8 @@ final class Notifier: Sendable {
await notificationDelegate.state.setPending(secret: secret, store: store)
let notificationCenter = UNUserNotificationCenter.current()
let notificationContent = UNMutableNotificationContent()
notificationContent.title = String(localized: "signed_notification_title_\(provenance.origin.displayName)")
notificationContent.subtitle = String(localized: "signed_notification_description_\(secret.name)")
notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName))
notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name))
notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
notificationContent.interruptionLevel = .timeSensitive
@ -85,11 +85,11 @@ final class Notifier: Sendable {
let notificationContent = UNMutableNotificationContent()
if update.critical {
notificationContent.interruptionLevel = .critical
notificationContent.title = String(localized: "update_notification_update_critical_title_\(update.name)")
notificationContent.title = String(localized: .updateNotificationUpdateCriticalTitle(updateName: update.name))
} else {
notificationContent.title = String(localized: "update_notification_update_normal_title_\(update.name)")
notificationContent.title = String(localized: .updateNotificationUpdateNormalTitle(updateName: update.name))
}
notificationContent.subtitle = String(localized: "update_notification_update_description")
notificationContent.subtitle = String(localized: .updateNotificationUpdateDescription)
notificationContent.body = update.body
notificationContent.categoryIdentifier = update.critical ? Constants.criticalUpdateCategoryIdentitifier : Constants.updateCategoryIdentitifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)

View File

@ -18,8 +18,9 @@
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF602780081600DF2006 /* SmartCardSecretKit */; };
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
500B93C32B478D8400E157DE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 500B93C22B478D8400E157DE /* Localizable.xcstrings */; };
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
@ -51,7 +52,6 @@
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
50E9CF422B51D596004AB36D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 500B93C22B478D8400E157DE /* Localizable.xcstrings */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -102,7 +102,7 @@
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
500B93C22B478D8400E157DE /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
@ -210,7 +210,7 @@
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
506772C62424784600034DED /* Credits.rtf */,
500B93C22B478D8400E157DE /* Localizable.xcstrings */,
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
50617D8823FCE48E0099B055 /* Preview Content */,
);
path = Secretive;
@ -404,7 +404,7 @@
buildActionMask = 2147483647;
files = (
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
500B93C32B478D8400E157DE /* Localizable.xcstrings in Resources */,
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
506772C72424784600034DED /* Credits.rtf in Resources */,
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
@ -416,7 +416,7 @@
buildActionMask = 2147483647;
files = (
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
50E9CF422B51D596004AB36D /* Localizable.xcstrings in Resources */,
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */,

View File

@ -59,18 +59,18 @@ struct Secretive: App {
}
.commands {
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("app_menu_new_secret_button") {
Button(.appMenuNewSecretButton) {
showingCreation = true
}
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
}
CommandGroup(replacing: .help) {
Button("app_menu_help_button") {
Button(.appMenuHelpButton) {
NSWorkspace.shared.open(Constants.helpURL)
}
}
CommandGroup(after: .help) {
Button("app_menu_setup_button") {
Button(.appMenuSetupButton) {
showingSetup = true
}
}

View File

@ -70,15 +70,15 @@ extension ContentView {
}
}
var updateNoticeContent: (LocalizedStringKey, Color)? {
var updateNoticeContent: (LocalizedStringResource, Color)? {
guard let update = updater.update else { return nil }
if update.critical {
return ("update_critical_notice_title", .red)
return (.updateCriticalNoticeTitle, .red)
} else {
if updater.testBuild {
return ("update_test_notice_title", .blue)
return (.updateTestNoticeTitle, .blue)
} else {
return ("update_normal_notice_title", .orange)
return (.updateNormalNoticeTitle, .orange)
}
}
}
@ -127,13 +127,13 @@ extension ContentView {
}, label: {
Group {
if hasRunSetup && !agentStatusChecker.running {
Text("agent_not_running_notice_title")
Text(.agentNotRunningNoticeTitle)
} else {
Text("agent_setup_notice_title")
Text(.agentSetupNoticeTitle)
}
}
.font(.headline)
.foregroundColor(.white)
})
.buttonStyle(ToolbarButtonStyle(color: .orange))
}
@ -144,7 +144,7 @@ extension ContentView {
showingAgentInfo = true
}, label: {
HStack {
Text("agent_running_notice_title")
Text(.agentRunningNoticeTitle)
.font(.headline)
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
Circle()
@ -155,10 +155,10 @@ extension ContentView {
.buttonStyle(ToolbarButtonStyle(lightColor: .black.opacity(0.05), darkColor: .white.opacity(0.05)))
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
VStack {
Text("agent_running_notice_detail_title")
Text(.agentRunningNoticeDetailTitle)
.font(.title)
.padding(5)
Text("agent_running_notice_detail_description")
Text(.agentRunningNoticeDetailDescription)
.frame(width: 300)
}
.padding()
@ -172,7 +172,7 @@ extension ContentView {
showingAppPathNotice = true
}, label: {
Group {
Text("app_not_in_applications_notice_title")
Text(.appNotInApplicationsNoticeTitle)
}
.font(.headline)
.foregroundColor(.white)
@ -184,7 +184,7 @@ extension ContentView {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 64)
Text("app_not_in_applications_notice_detail_description")
Text(.appNotInApplicationsNoticeDetailDescription)
.frame(maxWidth: 300)
}
.padding()

View File

@ -3,7 +3,7 @@ import UniformTypeIdentifiers
struct CopyableView: View {
var title: LocalizedStringKey
var title: LocalizedStringResource
var image: Image
var text: String

View File

@ -14,30 +14,30 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
HStack {
VStack {
HStack {
Text("create_secret_title")
Text(.createSecretTitle)
.font(.largeTitle)
Spacer()
}
HStack {
Text("create_secret_name_label")
TextField("create_secret_name_placeholder", text: $name)
Text(.createSecretNameLabel)
TextField(String(localized: .createSecretNamePlaceholder), text: $name)
.focusable()
}
ThumbnailPickerView(items: [
ThumbnailPickerView.Item(value: true, name: "create_secret_require_authentication_title", description: "create_secret_require_authentication_description", thumbnail: AuthenticationView()),
ThumbnailPickerView.Item(value: false, name: "create_secret_notify_title",
description: "create_secret_notify_description",
ThumbnailPickerView.Item(value: true, name: .createSecretRequireAuthenticationTitle, description: .createSecretRequireAuthenticationDescription, thumbnail: AuthenticationView()),
ThumbnailPickerView.Item(value: false, name: .createSecretNotifyTitle,
description: .createSecretNotifyDescription,
thumbnail: NotificationView())
], selection: $requiresAuthentication)
}
}
HStack {
Spacer()
Button("create_secret_cancel_button") {
Button(.createSecretCancelButton) {
showing = false
}
.keyboardShortcut(.cancelAction)
Button("create_secret_create_button", action: save)
Button(.createSecretCreateButton, action: save)
.disabled(name.isEmpty)
.keyboardShortcut(.defaultAction)
}
@ -98,11 +98,11 @@ extension ThumbnailPickerView {
struct Item<InnerValueType: Hashable>: Identifiable {
let id = UUID()
let value: InnerValueType
let name: LocalizedStringKey
let description: LocalizedStringKey
let name: LocalizedStringResource
let description: LocalizedStringResource
let thumbnail: AnyView
init<ViewType: View>(value: InnerValueType, name: LocalizedStringKey, description: LocalizedStringKey, thumbnail: ViewType) {
init<ViewType: View>(value: InnerValueType, name: LocalizedStringResource, description: LocalizedStringResource, thumbnail: ViewType) {
self.value = value
self.name = name
self.description = description

View File

@ -18,24 +18,24 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
.padding()
VStack {
HStack {
Text("delete_confirmation_title_\(secret.name)").bold()
Text(.deleteConfirmationTitle(secretName: secret.name)).bold()
Spacer()
}
HStack {
Text("delete_confirmation_description_\(secret.name)_\(secret.name)")
Text(.deleteConfirmationDescription(secretName: secret.name, confirmSecretName: secret.name))
Spacer()
}
HStack {
Text("delete_confirmation_confirm_name_label")
Text(.deleteConfirmationConfirmNameLabel)
TextField(secret.name, text: $confirm)
}
}
}
HStack {
Spacer()
Button("delete_confirmation_delete_button", action: delete)
Button(.deleteConfirmationDeleteButton, action: delete)
.disabled(confirm != secret.name)
Button("delete_confirmation_cancel_button") {
Button(.deleteConfirmationCancelButton) {
dismissalBlock(false)
}
.keyboardShortcut(.cancelAction)

View File

@ -18,9 +18,9 @@ struct EmptyStoreImmutableView: View {
var body: some View {
VStack {
Text("empty_store_nonmodifiable_title").bold()
Text("empty_store_nonmodifiable_description")
Text("empty_store_nonmodifiable_supported_key_types")
Text(.emptyStoreNonmodifiableTitle).bold()
Text(.emptyStoreNonmodifiableDescription)
Text(.emptyStoreNonmodifiableSupportedKeyTypes)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
@ -49,8 +49,8 @@ struct EmptyStoreModifiableView: View {
path.addLine(to: CGPoint(x: g.size.width - 3, y: 0))
}.fill()
}.frame(height: (windowGeometry.size.height/2) - 20).padding()
Text("empty_store_modifiable_click_here_title").bold()
Text("empty_store_modifiable_click_here_description")
Text(.emptyStoreModifiableClickHereTitle).bold()
Text(.emptyStoreModifiableClickHereDescription)
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}

View File

@ -4,10 +4,10 @@ struct NoStoresView: View {
var body: some View {
VStack {
Text("no_secure_storage_title")
Text(.noSecureStorageTitle)
.bold()
Text("no_secure_storage_description")
Link("no_secure_storage_yubico_link", destination: URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!)
Text(.noSecureStorageDescription)
Link(.noSecureStorageYubicoLink, destination: URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!)
}.padding()
}

View File

@ -18,7 +18,7 @@ struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
.padding()
VStack {
HStack {
Text("rename_title_\(secret.name)")
Text(.renameTitle(secretName: secret.name))
Spacer()
}
HStack {
@ -28,10 +28,10 @@ struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
}
HStack {
Spacer()
Button("rename_rename_button", action: rename)
Button(.renameRenameButton, action: rename)
.disabled(newName.count == 0)
.keyboardShortcut(.return)
Button("rename_cancel_button") {
Button(.renameCancelButton) {
dismissalBlock(false)
}.keyboardShortcut(.cancelAction)
}

View File

@ -12,16 +12,16 @@ struct SecretDetailView<SecretType: Secret>: View {
ScrollView {
Form {
Section {
CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret))
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret))
Spacer()
.frame(height: 20)
CopyableView(title: "secret_detail_md5_fingerprint_label", image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret))
CopyableView(title: .secretDetailMd5FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret))
Spacer()
.frame(height: 20)
CopyableView(title: "secret_detail_public_key_label", image: Image(systemName: "key"), text: keyString)
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
Spacer()
.frame(height: 20)
CopyableView(title: "secret_detail_public_key_path_label", image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret))
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret))
Spacer()
}
}

View File

@ -39,10 +39,10 @@ struct SecretListItemView: View {
.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { isRenaming = true }) {
Text("secret_list_rename_button")
Text(.secretListRenameButton)
}
Button(action: { isDeleting = true }) {
Text("secret_list_delete_button")
Text(.secretListDeleteButton)
}
}
}

View File

@ -61,7 +61,7 @@ struct StepView: View {
Circle()
.foregroundColor(.green)
.frame(width: Constants.circleWidth, height: Constants.circleWidth)
Text("setup_step_complete_symbol")
Text(.setupStepCompleteSymbol)
.foregroundColor(.white)
.bold()
} else {
@ -101,14 +101,14 @@ extension StepView {
struct SetupStepView<Content> : View where Content : View {
let title: LocalizedStringKey
let title: LocalizedStringResource
let image: Image
let bodyText: LocalizedStringKey
let buttonTitle: LocalizedStringKey
let bodyText: LocalizedStringResource
let buttonTitle: LocalizedStringResource
let buttonAction: () -> Void
let content: Content
init(title: LocalizedStringKey, image: Image, bodyText: LocalizedStringKey, buttonTitle: LocalizedStringKey, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) {
init(title: LocalizedStringResource, image: Image, bodyText: LocalizedStringResource, buttonTitle: LocalizedStringResource, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) {
self.title = title
self.image = image
self.bodyText = bodyText
@ -145,12 +145,12 @@ struct SecretAgentSetupView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: "setup_agent_title",
SetupStepView(title: .setupAgentTitle,
image: Image(nsImage: NSApplication.shared.applicationIconImage),
bodyText: "setup_agent_description",
buttonTitle: "setup_agent_install_button",
bodyText: .setupAgentDescription,
buttonTitle: .setupAgentInstallButton,
buttonAction: install) {
Text("setup_agent_activity_monitor_description")
Text(.setupAgentActivityMonitorDescription)
.multilineTextAlignment(.center)
}
}
@ -172,12 +172,12 @@ struct SSHAgentSetupView: View {
@State private var selectedShellInstruction: ShellConfigInstruction = controller.shellInstructions.first!
var body: some View {
SetupStepView(title: "setup_ssh_title",
SetupStepView(title: .setupSshTitle,
image: Image(systemName: "terminal"),
bodyText: "setup_ssh_description",
buttonTitle: "setup_ssh_added_manually_button",
bodyText: .setupSshDescription,
buttonTitle: .setupSshAddedManuallyButton,
buttonAction: buttonAction) {
Link("setup_third_party_faq_link", destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!)
Link(.setupThirdPartyFaqLink, destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!)
Picker(selection: $selectedShellInstruction, label: EmptyView()) {
ForEach(SSHAgentSetupView.controller.shellInstructions) { instruction in
Text(instruction.shell)
@ -185,8 +185,8 @@ struct SSHAgentSetupView: View {
.padding()
}
}.pickerStyle(SegmentedPickerStyle())
CopyableView(title: "setup_ssh_add_to_config_button_\(selectedShellInstruction.shellConfigPath)", image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text)
Button("setup_ssh_add_for_me_button") {
CopyableView(title: .setupSshAddToConfigButton(configPath: selectedShellInstruction.shellConfigPath), image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text)
Button(.setupSshAddForMeButton) {
let controller = ShellConfigurationController()
if controller.addToShell(shellInstructions: selectedShellInstruction) {
buttonAction()
@ -216,12 +216,12 @@ struct UpdaterExplainerView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: "setup_updates_title",
SetupStepView(title: .setupUpdatesTitle,
image: Image(systemName: "dot.radiowaves.left.and.right"),
bodyText: "setup_updates_description",
buttonTitle: "setup_updates_ok",
bodyText: .setupUpdatesDescription,
buttonTitle: .setupUpdatesOk,
buttonAction: buttonAction) {
Link("setup_updates_readmore", destination: SetupView.Constants.updaterFAQURL)
Link(.setupUpdatesReadmore, destination: SetupView.Constants.updaterFAQURL)
}
}

View File

@ -9,22 +9,22 @@ struct UpdateDetailView: View {
var body: some View {
VStack {
Text("update_version_name_\(update.name)").font(.title)
GroupBox(label: Text("update_release_notes_title")) {
Text(.updateVersionName(updateName: update.name)).font(.title)
GroupBox(label: Text(.updateReleaseNotesTitle)) {
ScrollView {
attributedBody
}
}
HStack {
if !update.critical {
Button("update_ignore_button") {
Button(.updateIgnoreButton) {
Task {
await updater.ignore(release: update)
}
}
Spacer()
}
Button("update_update_button") {
Button(.updateUpdateButton) {
NSWorkspace.shared.open(update.html_url)
}
.keyboardShortcut(.defaultAction)