Fix localization
This commit is contained in:
1 parent
58d2842a9e
commit
d2f3a80d8d
8 files changed
+290
-19
No files matched your search
@@ -47,7 +47,7 @@ nonisolated struct PortForwardingRule: Identifiable, Codable, Hashable, Sendable
|
||||
case .local:
|
||||
"\(bindAddress) → \(Self.address(targetHost ?? "", port: targetPort ?? 0))"
|
||||
case .remote:
|
||||
"SSH \(bindAddress) → This Mac \(Self.address(targetHost ?? "", port: targetPort ?? 0))"
|
||||
String(localized: "SSH \(bindAddress) → This Mac \(Self.address(targetHost ?? "", port: targetPort ?? 0))")
|
||||
case .dynamic: "SOCKS5 \(bindAddress)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ private struct SoftwareKeyEditor: View {
|
||||
}
|
||||
|
||||
let existing: SSHKeyRef?
|
||||
let title: String
|
||||
let title: LocalizedStringKey
|
||||
let symbol: String
|
||||
let close: () -> Void
|
||||
let loadPrivateKey: (() throws -> String)?
|
||||
@@ -433,7 +433,7 @@ private struct SoftwareKeyEditor: View {
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
|
||||
init(
|
||||
existing: SSHKeyRef? = nil, title: String,
|
||||
existing: SSHKeyRef? = nil, title: LocalizedStringKey,
|
||||
symbol: String, close: @escaping () -> Void,
|
||||
loadPrivateKey: (() throws -> String)? = nil,
|
||||
save:
|
||||
|
||||
@@ -91,7 +91,7 @@ struct NewIdentitySheet: View {
|
||||
|
||||
var body: some View {
|
||||
VaultDetailScaffold(
|
||||
title: existing == nil ? configuration.newTitle : configuration.editTitle,
|
||||
title: LocalizedStringKey(existing == nil ? configuration.newTitle : configuration.editTitle),
|
||||
symbol: "person.badge.key.fill", close: close,
|
||||
closeLabel: requestedClose == nil ? "Back" : "Close",
|
||||
closeSymbol: requestedClose == nil ? "chevron.backward" : "xmark"
|
||||
|
||||
@@ -161,7 +161,7 @@ private struct HostForwardingContextMenu: View {
|
||||
var body: some View {
|
||||
let isRunning = app.forwarding.isEnabled(rule.id)
|
||||
Button(
|
||||
isRunning ? "Stop" : "Start",
|
||||
isRunning ? String(localized: "Stop") : String(localized: "Start"),
|
||||
systemImage: isRunning ? MenuSymbol.stop : MenuSymbol.start,
|
||||
action: toggle
|
||||
)
|
||||
@@ -302,19 +302,20 @@ struct PortForwardingEditorSheet: View {
|
||||
}
|
||||
|
||||
private var bindPortLabel: String {
|
||||
draft.kind == .dynamic ? "SOCKS Listen Port" : "Listen Port"
|
||||
draft.kind == .dynamic ? String(localized: "SOCKS Listen Port") : String(localized: "Listen Port")
|
||||
}
|
||||
|
||||
private var bindAddressLabel: String {
|
||||
switch draft.kind {
|
||||
case .local: "Listen Address on This Mac"
|
||||
case .remote: "Listen Address on SSH Host"
|
||||
case .dynamic: "SOCKS Listen Address on This Mac"
|
||||
case .local: String(localized: "Listen Address on This Mac")
|
||||
case .remote: String(localized: "Listen Address on SSH Host")
|
||||
case .dynamic: String(localized: "SOCKS Listen Address on This Mac")
|
||||
}
|
||||
}
|
||||
|
||||
private var targetHostLabel: String {
|
||||
draft.kind == .remote ? "Destination Host on This Mac" : "Destination Host from SSH Host"
|
||||
draft.kind == .remote
|
||||
? String(localized: "Destination Host on This Mac") : String(localized: "Destination Host from SSH Host")
|
||||
}
|
||||
|
||||
private var exposesNetwork: Bool {
|
||||
@@ -346,8 +347,8 @@ struct PortForwardingEditorSheet: View {
|
||||
switch draft.kind {
|
||||
case .local: return "\(start) → \(ssh) → \(targetHost):\(targetPort)"
|
||||
case .remote:
|
||||
return "\(ssh) → listen \(start) → This Mac → \(targetHost):\(targetPort)"
|
||||
case .dynamic: return "SOCKS5 \(start) → \(ssh) → Requested Target"
|
||||
return String(localized: "\(ssh) → listen \(start) → This Mac → \(targetHost):\(targetPort)")
|
||||
case .dynamic: return String(localized: "SOCKS5 \(start) → \(ssh) → Requested Target")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,12 +99,12 @@ private nonisolated final class SSHForwardingWorker: @unchecked Sendable {
|
||||
guard let routeTransport else { throw TermindSSHError.disconnected }
|
||||
socket = routeTransport.socket
|
||||
guard let created = libssh2_session_init_ex(nil, nil, nil, nil) else {
|
||||
throw TermindSSHError.library("Unable to create an SSH forwarding session.")
|
||||
throw TermindSSHError.library(String(localized: "Unable to create an SSH forwarding session."))
|
||||
}
|
||||
session = created
|
||||
libssh2_session_set_blocking(created, 1)
|
||||
guard libssh2_session_handshake(created, socket) == 0 else {
|
||||
throw SSHSessionWorker.sessionError(created, prefix: "SSH handshake failed")
|
||||
throw SSHSessionWorker.sessionError(created, prefix: String(localized: "SSH handshake failed"))
|
||||
}
|
||||
let observed = try SSHSessionWorker.observation(
|
||||
host: configuration.host, port: configuration.port, session: created
|
||||
@@ -146,7 +146,8 @@ private nonisolated final class SSHForwardingWorker: @unchecked Sendable {
|
||||
)
|
||||
} catch {
|
||||
throw TermindSSHError.route(
|
||||
"Unable to listen on \(rule.bindAddress): \(error.localizedDescription)"
|
||||
String(
|
||||
localized: "Unable to listen on \(rule.bindAddress): \(error.localizedDescription)")
|
||||
)
|
||||
}
|
||||
case .remote:
|
||||
@@ -159,7 +160,7 @@ private nonisolated final class SSHForwardingWorker: @unchecked Sendable {
|
||||
guard let listener else {
|
||||
throw TermindSSHError.route(
|
||||
SSHSessionWorker.sessionError(
|
||||
session, prefix: "Unable to start remote forwarding"
|
||||
session, prefix: String(localized: "Unable to start remote forwarding")
|
||||
).localizedDescription
|
||||
)
|
||||
}
|
||||
@@ -214,7 +215,7 @@ private nonisolated final class SSHForwardingWorker: @unchecked Sendable {
|
||||
let keepalive = libssh2_keepalive_send(session, &next)
|
||||
if keepalive < 0, keepalive != LIBSSH2_ERROR_EAGAIN {
|
||||
let message = SSHSessionWorker.sessionError(
|
||||
session, prefix: "The forwarding SSH connection closed"
|
||||
session, prefix: String(localized: "The forwarding SSH connection closed")
|
||||
).localizedDescription
|
||||
cleanup()
|
||||
failureHandler(message)
|
||||
|
||||
@@ -223,6 +223,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ → listen %@ → This Mac → %@:%@" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@→监听%2$@→这台Mac→%3$@:%4$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@%@ of %@ (%lld%%)" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@@ -3634,6 +3644,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Destination Host from SSH Host" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SSH主机可访问的目标主机"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Destination Host on This Mac" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "这台Mac可访问的目标主机"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Destination Port" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -4006,6 +4036,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Group" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑组"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Host" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑主机"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Identity" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -4016,6 +4066,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Key" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑密钥"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Package" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑包"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Path" : {
|
||||
"comment" : "A button that opens a dialog to edit the current path.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
@@ -4048,6 +4118,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Port Forward" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑端口转发"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Edit Snippet" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "编辑片段"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Effort" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -5940,6 +6030,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Listen Address on SSH Host" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SSH主机上的监听地址"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Listen Address on This Mac" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "这台Mac上的监听地址"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Listen Port" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "监听端口"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Listening on localhost:%@" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -6452,6 +6572,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"New Port Forward" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "新建端口转发"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"New Provider" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -9152,6 +9282,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SSH handshake failed" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SSH握手失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Save" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -10068,6 +10208,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS Listen Address on This Mac" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "这台Mac上的SOCKS监听地址"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS Listen Port" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SOCKS监听端口"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS5" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -10078,6 +10238,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS5 %@ → %@ → Requested Target" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SOCKS5 %1$@→%2$@→请求目标"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SOCKS5 on This Mac → SSH Host → Requested Target" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -10160,6 +10330,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SSH %@ → This Mac %@" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SSH %1$@→这台Mac %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SSH Command" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -11022,6 +11202,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"The forwarding SSH connection closed" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "用于转发的SSH连接已关闭"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"The forwarding host is unavailable." : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -12238,6 +12428,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to create an SSH forwarding session." : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "无法创建SSH转发会话。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to generate a secure token." : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
@@ -12248,6 +12448,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to listen on %@: %@" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "无法监听%1$@:%2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to start remote forwarding" : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "无法启动远程转发"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unable to store the token." : {
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
|
||||
@@ -119,10 +119,10 @@ struct SheetScaffold<Header: View, Content: View, Footer: View>: View {
|
||||
/// resembles the manager toolbars rather than a floating sheet: a clear close
|
||||
/// affordance, a quiet title, scrollable work, and actions anchored at the foot.
|
||||
struct VaultDetailScaffold<Content: View, Footer: View>: View {
|
||||
let title: String
|
||||
let title: LocalizedStringKey
|
||||
let symbol: String
|
||||
let close: () -> Void
|
||||
var closeLabel = "Close"
|
||||
var closeLabel: LocalizedStringKey = "Close"
|
||||
var closeSymbol = "xmark"
|
||||
@ViewBuilder let content: Content
|
||||
@ViewBuilder let footer: Footer
|
||||
|
||||
@@ -66,6 +66,55 @@ struct AppLanguageTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func editorLabelsAndForwardingMessagesHaveChineseTranslations() throws {
|
||||
let repository = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent().deletingLastPathComponent()
|
||||
let url = repository.appending(path: "Termind/Resources/Localizable.xcstrings")
|
||||
let root = try #require(
|
||||
JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any]
|
||||
)
|
||||
let strings = try #require(root["strings"] as? [String: Any])
|
||||
let labels = [
|
||||
"New Host", "Edit Host", "Import Key", "Generate Key", "Edit Key", "Export to Hosts",
|
||||
"New Group", "Edit Group", "New Snippet", "Edit Snippet", "New Package", "Edit Package",
|
||||
"New Identity", "Edit Identity", "Add Hosts", "Close",
|
||||
"New Port Forward", "Edit Port Forward", "Back", "Start", "Stop",
|
||||
"SOCKS Listen Port", "Listen Port", "Listen Address on This Mac",
|
||||
"Listen Address on SSH Host", "SOCKS Listen Address on This Mac",
|
||||
"Destination Host on This Mac", "Destination Host from SSH Host",
|
||||
"Unable to create an SSH forwarding session.", "SSH handshake failed",
|
||||
"Unable to start remote forwarding", "The forwarding SSH connection closed",
|
||||
]
|
||||
let interpolations: [String: [String]] = [
|
||||
"%@ → listen %@ → This Mac → %@:%@": ["jump.example", "[::1]:8080", "db.internal", "5432"],
|
||||
"SOCKS5 %@ → %@ → Requested Target": ["[::1]:1080", "jump.example"],
|
||||
"SSH %@ → This Mac %@": ["[::1]:8080", "[2001:db8::1]:443"],
|
||||
"Unable to listen on %@: %@": ["[::1]:8080", "Address already in use"],
|
||||
]
|
||||
|
||||
for key in labels + interpolations.keys.sorted() {
|
||||
let entry = try #require(strings[key] as? [String: Any], "Missing editor or forwarding string: \(key)")
|
||||
let localizations = try #require(entry["localizations"] as? [String: Any])
|
||||
let chinese = try #require(localizations["zh-Hans"] as? [String: Any])
|
||||
let unit = try #require(chinese["stringUnit"] as? [String: Any])
|
||||
let translation = try #require(unit["value"] as? String)
|
||||
#expect(unit["state"] as? String == "translated")
|
||||
#expect(translation.range(of: "\\p{Han}", options: .regularExpression) != nil)
|
||||
|
||||
if let arguments = interpolations[key] {
|
||||
let placeholders = translation.matches(of: /%(?:[1-9][0-9]*\$)?@/)
|
||||
#expect(placeholders.count == arguments.count, "Placeholder mismatch: \(key)")
|
||||
let rendered = String(format: translation, arguments: arguments)
|
||||
var remaining = rendered[...]
|
||||
for argument in arguments {
|
||||
let range = try #require(remaining.range(of: argument), "Lost or reordered value: \(key)")
|
||||
remaining = remaining[range.upperBound...]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func countedToolResourcesUseCatalogPluralRules() throws {
|
||||
let repository = URL(fileURLWithPath: #filePath)
|
||||
|
||||
Reference in new issue
Block a user