diff --git a/ApplicationLibrary/Views/Abstract/StringCompat.swift b/ApplicationLibrary/Views/Abstract/StringCompat.swift new file mode 100644 index 0000000..72868af --- /dev/null +++ b/ApplicationLibrary/Views/Abstract/StringCompat.swift @@ -0,0 +1,12 @@ +import Foundation +import SwiftUI + +public extension Bool { + func toString() -> String { + if self { + return String(localized: "true") + } else { + return String(localized: "false") + } + } +} diff --git a/ApplicationLibrary/Views/Connections/ConnectionDetailsView.swift b/ApplicationLibrary/Views/Connections/ConnectionDetailsView.swift index 6a68aae..5e4dd68 100644 --- a/ApplicationLibrary/Views/Connections/ConnectionDetailsView.swift +++ b/ApplicationLibrary/Views/Connections/ConnectionDetailsView.swift @@ -20,8 +20,8 @@ public struct ConnectionDetailsView: View { if let closedAt = connection.closedAt { FormTextItem("Closed At", closedAt.myFormat) } - FormTextItem("Upload", LibboxFormatBytes(connection.uploadTotal)) - FormTextItem("Download", LibboxFormatBytes(connection.downloadTotal)) + FormTextItem("Uplink", LibboxFormatBytes(connection.uploadTotal)) + FormTextItem("Downlink", LibboxFormatBytes(connection.downloadTotal)) Section("Metadata") { FormTextItem("Inbound", connection.inbound) FormTextItem("Inbound Type", connection.inboundType) diff --git a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift index 90cc5c8..5acdd5c 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift @@ -31,11 +31,11 @@ public extension DashboardPage { var title: String { switch self { case .overview: - return NSLocalizedString("Overview", comment: "") + return String(localized: "Overview") case .groups: - return NSLocalizedString("Groups", comment: "") + return String(localized: "Groups") case .connections: - return NSLocalizedString("Connections", comment: "") + return String(localized: "Connections") } } diff --git a/ApplicationLibrary/Views/Dashboard/DashboardView.swift b/ApplicationLibrary/Views/Dashboard/DashboardView.swift index b73b056..c773473 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardView.swift @@ -183,7 +183,7 @@ public struct DashboardView: View { await MainActor.run { alert = Alert( title: Text("Full Disk Access permission is required"), - message: Text("Please grant the permission for SFMExtension, then we can continue."), + message: Text("Please grant the permission for **SFMExtension**, then we can continue."), primaryButton: .default(Text("Authorize"), action: openFDASettings), secondaryButton: .cancel() ) diff --git a/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift b/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift index 3ab21f1..7907217 100644 --- a/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift +++ b/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift @@ -27,49 +27,49 @@ public struct ExtensionStatusView: View { VStack { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) { if ApplicationLibrary.inPreview { - StatusItem("Status") { - StatusLine("Memory", "6.4 MB") - StatusLine("Goroutines", "89") + StatusItem(String(localized: "Status")) { + StatusLine(String(localized: "Memory"), "6.4 MB") + StatusLine(String(localized: "Goroutines"), "89") } - StatusItem("Connections") { - StatusLine("Inbound", "34") - StatusLine("Outbound", "28") + StatusItem(String(localized: "Connections")) { + StatusLine(String(localized: "Inbound"), "34") + StatusLine(String(localized: "Outbound"), "28") } - StatusItem("Traffic") { - StatusLine("Uplink", "38 B/s") - StatusLine("Downlink", "249 MB/s") + StatusItem(String(localized: "Traffic")) { + StatusLine(String(localized: "Uplink"), "38 B/s") + StatusLine(String(localized: "Downlink"), "249 MB/s") } - StatusItem("TrafficTotal") { - StatusLine("Uplink", "52 MB") - StatusLine("Downlink", "5.6 GB") + StatusItem(String(localized: "Traffic Total")) { + StatusLine(String(localized: "Uplink"), "52 MB") + StatusLine(String(localized: "Downlink"), "5.6 GB") } } else if let message = commandClient.status { - StatusItem("Status") { - StatusLine("Memory", LibboxFormatMemoryBytes(message.memory)) - StatusLine("Goroutines", "\(message.goroutines)") + StatusItem(String(localized: "Status")) { + StatusLine(String(localized: "Memory"), LibboxFormatMemoryBytes(message.memory)) + StatusLine(String(localized: "Goroutines"), "\(message.goroutines)") } - StatusItem("Connections") { - StatusLine("Inbound", "\(message.connectionsIn)") - StatusLine("Outbound", "\(message.connectionsOut)") + StatusItem(String(localized: "Connections")) { + StatusLine(String(localized: "Inbound"), "\(message.connectionsIn)") + StatusLine(String(localized: "Outbound"), "\(message.connectionsOut)") } if message.trafficAvailable { - StatusItem("Traffic") { - StatusLine("Uplink", "\(LibboxFormatBytes(message.uplink))/s") - StatusLine("Downlink", "\(LibboxFormatBytes(message.downlink))/s") + StatusItem(String(localized: "Traffic")) { + StatusLine(String(localized: "Uplink"), "\(LibboxFormatBytes(message.uplink))/s") + StatusLine(String(localized: "Downlink"), "\(LibboxFormatBytes(message.downlink))/s") } - StatusItem("Traffic Total") { - StatusLine("Uplink", LibboxFormatBytes(message.uplinkTotal)) - StatusLine("Downlink", LibboxFormatBytes(message.downlinkTotal)) + StatusItem(String(localized: "Traffic Total")) { + StatusLine(String(localized: "Uplink"), LibboxFormatBytes(message.uplinkTotal)) + StatusLine(String(localized: "Downlink"), LibboxFormatBytes(message.downlinkTotal)) } } } else { - StatusItem("Status") { - StatusLine("Memory", "...") - StatusLine("Goroutines", "...") + StatusItem(String(localized: "Status")) { + StatusLine(String(localized: "Memory"), "...") + StatusLine(String(localized: "Goroutines"), "...") } - StatusItem("Connections") { - StatusLine("Inbound", "...") - StatusLine("Outbound", "...") + StatusItem(String(localized: "Connections")) { + StatusLine(String(localized: "Inbound"), "...") + StatusLine(String(localized: "Outbound"), "...") } } }.background { diff --git a/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift b/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift index 7e6c0b7..f9b0188 100644 --- a/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift +++ b/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift @@ -26,7 +26,7 @@ do { if let result = try await SystemExtension.install() { if result == .willCompleteAfterReboot { - alert = Alert(errorMessage: "Need Reboot") + alert = Alert(errorMessage: String(localized: "Need Reboot")) } } await callback() diff --git a/ApplicationLibrary/Views/NavigationPage.swift b/ApplicationLibrary/Views/NavigationPage.swift index 346db43..00c9c35 100644 --- a/ApplicationLibrary/Views/NavigationPage.swift +++ b/ApplicationLibrary/Views/NavigationPage.swift @@ -32,19 +32,19 @@ public extension NavigationPage { var title: String { switch self { case .dashboard: - return NSLocalizedString("Dashboard", comment: "") + return String(localized: "Dashboard") #if os(macOS) case .groups: - return NSLocalizedString("Groups", comment: "") + return String(localized: "Groups") case .connections: return NSLocalizedString("Connections", comment: "") #endif case .logs: - return NSLocalizedString("Logs", comment: "") + return String(localized: "Logs") case .profiles: - return NSLocalizedString("Profiles", comment: "") + return String(localized: "Profiles") case .settings: - return NSLocalizedString("Settings", comment: "") + return String(localized: "Settings") } } diff --git a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift index 8e16499..994d34c 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift @@ -104,9 +104,9 @@ private var navigationTitle: String { if readOnly { - return "View Content" + return String(localized: "View Content") } else { - return "Edit Content" + return String(localized: "Edit Content") } } diff --git a/ApplicationLibrary/Views/Profile/EditProfileView.swift b/ApplicationLibrary/Views/Profile/EditProfileView.swift index 25043c0..647e836 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileView.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileView.swift @@ -17,7 +17,7 @@ public struct EditProfileView: View { public init() {} public var body: some View { FormView { - FormItem("Name") { + FormItem(String(localized: "Name")) { TextField("Name", text: $profile.name, prompt: Text("Required")) .multilineTextAlignment(.trailing) } @@ -31,17 +31,17 @@ public struct EditProfileView: View { } .disabled(true) if profile.type == .icloud { - FormItem("Path") { + FormItem(String(localized: "Path")) { TextField("Path", text: $profile.path, prompt: Text("Required")) .multilineTextAlignment(.trailing) } } else if profile.type == .remote { - FormItem("URL") { + FormItem(String(localized: "URL")) { TextField("URL", text: $profile.remoteURL.unwrapped(""), prompt: Text("Required")) .multilineTextAlignment(.trailing) } Toggle("Auto Update", isOn: $profile.autoUpdate) - FormItem("Auto Update Interval") { + FormItem(String(localized: "Auto Update Interval")) { TextField("Auto Update Interval", text: $profile.autoUpdateInterval.stringBinding(defaultValue: 60), prompt: Text("In Minutes")) .multilineTextAlignment(.trailing) #if os(iOS) diff --git a/ApplicationLibrary/Views/Profile/NewProfileView.swift b/ApplicationLibrary/Views/Profile/NewProfileView.swift index 9363de9..ecde815 100644 --- a/ApplicationLibrary/Views/Profile/NewProfileView.swift +++ b/ApplicationLibrary/Views/Profile/NewProfileView.swift @@ -34,7 +34,7 @@ public struct NewProfileView: View { public var body: some View { FormView { - FormItem("Name") { + FormItem(String(localized: "Name")) { TextField("Name", text: $profileName, prompt: Text("Required")) .multilineTextAlignment(.trailing) } @@ -76,17 +76,17 @@ public struct NewProfileView: View { } } } else if profileType == .icloud { - FormItem("Path") { + FormItem(String(localized: "Path")) { TextField("Path", text: $remotePath, prompt: Text("Required")) .multilineTextAlignment(.trailing) } } else if profileType == .remote { - FormItem("URL") { + FormItem(String(localized: "URL")) { TextField("URL", text: $remotePath, prompt: Text("Required")) .multilineTextAlignment(.trailing) } Toggle("Auto Update", isOn: $autoUpdate) - FormItem("Auto Update Interval") { + FormItem(String(localized: "Auto Update Interval")) { TextField("Auto Update Interval", text: $autoUpdateInterval.stringBinding(defaultValue: 60), prompt: Text("In Minutes")) .multilineTextAlignment(.trailing) #if os(iOS) @@ -135,15 +135,15 @@ public struct NewProfileView: View { isSaving = false } if profileName.isEmpty { - alert = Alert(errorMessage: "Missing profile name") + alert = Alert(errorMessage: String(localized: "Missing profile name")) return } if remotePath.isEmpty { if profileType == .icloud { - alert = Alert(errorMessage: "Missing path") + alert = Alert(errorMessage: String(localized: "Missing path")) return } else if profileType == .remote { - alert = Alert(errorMessage: "Missing URL") + alert = Alert(errorMessage: String(localized: "Missing URL")) return } } diff --git a/ApplicationLibrary/Views/Setting/MacAppView.swift b/ApplicationLibrary/Views/Setting/MacAppView.swift index 8573550..3c3285b 100644 --- a/ApplicationLibrary/Views/Setting/MacAppView.swift +++ b/ApplicationLibrary/Views/Setting/MacAppView.swift @@ -1,30 +1,33 @@ + +import Library +import SwiftUI + #if os(macOS) - import AppKit - import Library import ServiceManagement - import SwiftUI +#endif - public struct MacAppView: View { - @State private var isLoading = true +public struct AppView: View { + @State private var isLoading = true - @State private var startAtLogin = false - @Environment(\.showMenuBarExtra) private var showMenuBarExtra - @State private var menuBarExtraInBackground = false + @State private var startAtLogin = false + @Environment(\.showMenuBarExtra) private var showMenuBarExtra + @State private var menuBarExtraInBackground = false - @State private var alert: Alert? + @State private var alert: Alert? - public init() {} - public var body: some View { - viewBuilder { - if isLoading { - ProgressView().onAppear { - Task { - await loadSettings() - } + public init() {} + public var body: some View { + viewBuilder { + if isLoading { + ProgressView().onAppear { + Task { + await loadSettings() } - } else { - FormView { + } + } else { + FormView { + #if os(macOS) FormToggle("Start At Login", "Launch the application when the system is logged in. If enabled at the same time as `Show in Menu Bar` and `Keep Menu Bar in Background`, the application interface will not be opened automatically.", $startAtLogin) { newValue in updateLoginItems(newValue) } @@ -66,21 +69,26 @@ } } } - } + #endif } } - .alertBinding($alert) - .navigationTitle("App") - #if os(iOS) - .navigationBarTitleDisplayMode(.inline) - #endif } + .alertBinding($alert) + .navigationTitle("App") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } - private func loadSettings() async { + private func loadSettings() async { + #if os(macOS) startAtLogin = SMAppService.mainApp.status == .enabled menuBarExtraInBackground = await SharedPreferences.menuBarExtraInBackground.get() - isLoading = false - } + #endif + isLoading = false + } + + #if os(macOS) private func updateLoginItems(_ startAtLogin: Bool) { do { @@ -143,6 +151,6 @@ alert = Alert(error) } } - } -#endif + #endif +} diff --git a/ApplicationLibrary/Views/Setting/ServiceLogView.swift b/ApplicationLibrary/Views/Setting/ServiceLogView.swift index 9df8639..6522d4e 100644 --- a/ApplicationLibrary/Views/Setting/ServiceLogView.swift +++ b/ApplicationLibrary/Views/Setting/ServiceLogView.swift @@ -87,11 +87,11 @@ public struct ServiceLogView: View { guard let value = element.value as? Int8, value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } - var deviceInfo = "Machine: " + machineName + "\n" + var deviceInfo = String("Machine: ") + machineName + "\n" #if os(iOS) - await deviceInfo += "System: " + (UIDevice.current.systemName) + " " + (UIDevice.current.systemVersion) + "\n" + await deviceInfo += String("System: ") + (UIDevice.current.systemName) + " " + (UIDevice.current.systemVersion) + "\n" #elseif os(macOS) - deviceInfo += "System: macOS " + ProcessInfo().operatingSystemVersionString + "\n" + deviceInfo += String("System: ") + "macOS " + ProcessInfo().operatingSystemVersionString + "\n" #endif content = deviceInfo + "\n" + content } diff --git a/ApplicationLibrary/Views/Setting/SettingView.swift b/ApplicationLibrary/Views/Setting/SettingView.swift index 1248be3..de965fe 100644 --- a/ApplicationLibrary/Views/Setting/SettingView.swift +++ b/ApplicationLibrary/Views/Setting/SettingView.swift @@ -22,18 +22,18 @@ public struct SettingView: View { switch self { #if os(macOS) case .app: - return NSLocalizedString("App", comment: "") + return String(localized: "App") #endif case .core: - return NSLocalizedString("Core", comment: "") + return String(localized: "Core") case .packetTunnel: - return NSLocalizedString("Packet Tunnel", comment: "") + return String(localized: "Packet Tunnel") case .onDemandRules: - return NSLocalizedString("On Demand Rules", comment: "") + return String(localized: "On Demand Rules") case .profileOverride: - return NSLocalizedString("Profile Override", comment: "") + return String(localized: "Profile Override") case .sponsors: - return NSLocalizedString("Sponsors", comment: "") + return String(localized: "Sponsors") } } @@ -62,7 +62,7 @@ public struct SettingView: View { switch self { #if os(macOS) case .app: - MacAppView() + AppView() #endif case .core: CoreView() @@ -106,11 +106,29 @@ public struct SettingView: View { } #if !os(tvOS) Section("About") { - Link(destination: URL(string: "https://sing-box.sagernet.org/")!) { + Link(destination: URL(string: String(localized: "https://sing-box.sagernet.org/"))!) { Label("Documentation", systemImage: "doc.on.doc.fill") } .buttonStyle(.plain) .foregroundColor(.accentColor) + .contextMenu { + Link(destination: URL(string: String(localized: "https://sing-box.sagernet.org/changelog/"))!) { + Text("Changelog") + } + Link(destination: URL(string: String(localized: "https://sing-box.sagernet.org/configuration/"))!) { + Text("Configuration") + } + } + Link(destination: URL(string: String("https://github.com/SagerNet/sing-box"))!) { + Label("Source Code", systemImage: "pills.fill") + } + .buttonStyle(.plain) + .foregroundColor(.accentColor) + .contextMenu { + Link(destination: URL(string: String("https://github.com/SagerNet/sing-box/releases"))!) { + Text("Releases") + } + } RequestReviewButton { Label("Rate on the App Store", systemImage: "text.bubble.fill") } @@ -145,7 +163,7 @@ public struct SettingView: View { } } } else { - Text(taiwanFlagAvailable.description) + Text(taiwanFlagAvailable.toString()) } } } diff --git a/ApplicationLibrary/Views/Setting/SponsorsView.swift b/ApplicationLibrary/Views/Setting/SponsorsView.swift index 60061f7..fa6bf64 100644 --- a/ApplicationLibrary/Views/Setting/SponsorsView.swift +++ b/ApplicationLibrary/Views/Setting/SponsorsView.swift @@ -14,10 +14,10 @@ public struct SponsorsView: View { .frame(maxWidth: .infinity, alignment: .leading) } - FormButton("GitHub Sponsor (recommended)") { + FormButton(String(localized: "GitHub Sponsors (recommended)")) { openURL(URL(string: "https://github.com/sponsors/nekohasekai")!) } - FormButton("Other methods") { + FormButton(String(localized: "Other methods")) { openURL(URL(string: "https://sekai.icu/sponsors/")!) } } diff --git a/Library/Database/SharedPreferences.swift b/Library/Database/SharedPreferences.swift index 7b93f4e..e62151e 100644 --- a/Library/Database/SharedPreferences.swift +++ b/Library/Database/SharedPreferences.swift @@ -1,6 +1,8 @@ import Foundation public enum SharedPreferences { + public static let language = Preference("language", defaultValue: "") + public static let selectedProfileID = Preference("selected_profile_id", defaultValue: -1) #if os(macOS) diff --git a/Library/Network/HTTPClient.swift b/Library/Network/HTTPClient.swift index 2206c5a..8e247fe 100644 --- a/Library/Network/HTTPClient.swift +++ b/Library/Network/HTTPClient.swift @@ -10,6 +10,8 @@ public class HTTPClient { userAgent += Bundle.main.versionNumber userAgent += "; sing-box " userAgent += LibboxVersion() + userAgent += "; language " + userAgent += Locale.current.identifier userAgent += ")" return userAgent } diff --git a/Localizable.xcstrings b/Localizable.xcstrings new file mode 100644 index 0000000..a9a0d89 --- /dev/null +++ b/Localizable.xcstrings @@ -0,0 +1,1626 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + "shouldTranslate" : false + }, + "**If I’ve defended your modern life, please consider sponsoring me.**" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "**支持我的工作**" + } + } + } + }, + "%@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@" + } + } + }, + "shouldTranslate" : false + }, + "%lld" : { + "shouldTranslate" : false + }, + "↑ %@" : { + "shouldTranslate" : false + }, + "↑ %@/s" : { + "shouldTranslate" : false + }, + "↓ %@" : { + "shouldTranslate" : false + }, + "↓ %@/s" : { + "shouldTranslate" : false + }, + "About" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关于" + } + } + } + }, + "Action" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "动作" + } + } + } + }, + "Active" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活动" + } + } + } + }, + "All" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有" + } + } + } + }, + "Always On" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "始终启动" + } + } + } + }, + "App" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用" + } + } + } + }, + "Append `0.0.0.0/31` to `inet4_route_exclude_address` if not exists." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将会附加 `0.0.0.0/31` 到 `inet4_route_exclude_address`。" + } + } + } + }, + "Append `push.apple.com` to `bypass_domain`, and `17.0.0.0/8` to `inet4_route_exclude_address`." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将会附加 `push.apple.com` 到 `bypass_domain`, 附加 `17.0.0.0/8` 到 `inet4_route_exclude_address`。" + } + } + } + }, + "Are you sure to import profile %@?" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要导入 %@ 吗?" + } + } + } + }, + "Are you sure to import remote profile %@? You will connect to %@ to download the configuration." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Are you sure to import remote profile %1$@? You will connect to %2$@ to download the configuration." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要导入 %1$@ 吗?将会连接到 %2$@ 下载配置。" + } + } + } + }, + "Authorize" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "授权" + } + } + } + }, + "Auto Update" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动更新" + } + } + } + }, + "Auto Update Interval" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动更新间隔" + } + } + } + }, + "By default, segment routing is used in `auto_route` instead of global routing.\nIf `*_` exists in the configuration, this item will not take effect on the corresponding network (commonly used to resolve HomeKit compatibility issues)." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在默认情况下,`auto_route` 中使用分段路由,而非全局路由。\n若配置中存在 `*_`,则此项不会在对应网络上生效(常用于解决 HomeKit 兼容性问题)。" + } + } + } + }, + "Chain" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "链" + } + } + } + }, + "Changelog" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新日志" + } + } + } + }, + "Choose" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选取" + } + } + } + }, + "Close" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + } + } + }, + "Close All Connections" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭所有连接" + } + } + } + }, + "Closed" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已关闭" + } + } + } + }, + "Closed At" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭时间" + } + } + } + }, + "Configuration" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" + } + } + } + }, + "Connection" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接" + } + } + } + }, + "Connections" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接" + } + } + } + }, + "Copy" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制" + } + } + } + }, + "Core" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "核心" + } + } + } + }, + "Create" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建" + } + } + } + }, + "Create New" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建" + } + } + } + }, + "Created At" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建时间" + } + } + } + }, + "Dashboard" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仪表" + } + } + } + }, + "Data Size" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "数据大小" + } + } + } + }, + "Date" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日期" + } + } + } + }, + "Debug" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "调试" + } + } + } + }, + "Delete" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除" + } + } + } + }, + "Deprecated Warning" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "弃用警告" + } + } + } + }, + "Destination" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "mub" + } + } + } + }, + "Destroy" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清理" + } + } + } + }, + "Do not enforce memory limits on sing-box. Will cause OOM on non-jailbroken iOS and tvOS devices." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "不要对 sing-box 强制执行内存限制。这将导致未越狱的 iOS 和 tvOS 设备出现 OOM。" + } + } + } + }, + "Documentation" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文档" + } + } + } + }, + "Domain" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "域" + } + } + } + }, + "Downlink" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "下载" + } + } + } + }, + "Edit Content" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑内容" + } + } + } + }, + "Edit Profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑配置" + } + } + } + }, + "Empty connections" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无连接" + } + } + } + }, + "Empty content" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无内容" + } + } + } + }, + "Empty groups" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无分组" + } + } + } + }, + "Empty logs" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无日志" + } + } + } + }, + "Empty profiles" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无配置" + } + } + } + }, + "Enabled" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用" + } + } + } + }, + "enforceRoutes" : { + "shouldTranslate" : false + }, + "Error" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "错误" + } + } + } + }, + "Exclude APNs Route" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "排除 APNs 路由" + } + } + } + }, + "excludeAPNs" : { + "shouldTranslate" : false + }, + "excludeCellularServices" : { + "shouldTranslate" : false + }, + "excludeLocalNetworks" : { + "shouldTranslate" : false + }, + "Export" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出" + } + } + } + }, + "false" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "否" + } + } + } + }, + "File" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件" + } + } + } + }, + "File Path" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件路径" + } + } + } + }, + "Filter" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "筛选" + } + } + } + }, + "From Outbound" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "来自出站" + } + } + } + }, + "Full Disk Access permission is required" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要完全磁盘访问权限" + } + } + } + }, + "GitHub Sponsors (recommended)" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过 Github Sponsors (推荐)" + } + } + } + }, + "Goroutines" : { + "shouldTranslate" : false + }, + "Groups" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "组" + } + } + } + }, + "Handled unknown URL %@" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法处理的URL: %@" + } + } + } + }, + "Hide VPN Icon" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏 VPN 图标" + } + } + } + }, + "HTTP Proxy" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "HTTP 代理" + } + } + } + }, + "https://sing-box.sagernet.org/" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://sing-box.sagernet.org/zh/" + } + } + } + }, + "https://sing-box.sagernet.org/changelog/" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://sing-box.sagernet.org/zh/changelog/" + } + } + } + }, + "https://sing-box.sagernet.org/configuration/" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://sing-box.sagernet.org/zh/configuration/" + } + } + } + }, + "iCloud" : { + "shouldTranslate" : false + }, + "If this property is true when the **includeAllNetworks** property is false, the system scopes the included routes to the VPN and the excluded routes to the current primary network interface. This property supersedes the system routing table and scoping operations by apps.\n\nIf you set both the **enforceRoutes** and **excludeLocalNetworks** properties to true, the system excludes network connections to hosts on the local network.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3689459-enforceroutes)" : { + "shouldTranslate" : false + }, + "If this property is true, the system excludes Apple Push Notification services (APNs) traffic, but only when the **includeAllNetworks** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns)" : { + "shouldTranslate" : false + }, + "If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesn’t impact services that use the cellular network only — such as VoLTE — which the system automatically excludes.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices)" : { + "shouldTranslate" : false + }, + "If this property is true, the system excludes network connections to hosts on the local network — such as AirPlay, AirDrop, and CarPlay — but only when the **includeAllNetworks** or **enforceRoutes** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks)" : { + "shouldTranslate" : false + }, + "If this property is true, the system routes network traffic through the tunnel except traffic for designated system services necessary for maintaining expected device functionality. You can exclude some types of traffic using the **excludeAPNs**, **excludeLocalNetworks**, and **excludeCellularServices** properties in combination with this property.\n\nwhen enabled, the default TUN stack is changed to `gvisor`, and the `system` and `mixed` stacks are not available.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3131931-includeallnetworks)" : { + "shouldTranslate" : false + }, + "Ignore Memory Limit" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略内存限制" + } + } + } + }, + "Implement always-on via on-demand rules.\n\nThis should not be an intended use of the API, so you cannot disable VPN in system settings. To stop the service manually, use the in-app interface or simply delete the VPN profile." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过按需规则实现始终启用。\n\n这不应是 API 的预期用途,因此您无法在系统设置中禁用 VPN。要手动停止服务,请使用应用内界面或直接删除 VPN 配置文件。" + } + } + } + }, + "Import" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入" + } + } + } + }, + "Import Profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入配置文件" + } + } + } + }, + "Import Remote Profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入远程配置文件" + } + } + } + }, + "In Minutes" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分钟" + } + } + } + }, + "Inbound" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "入站" + } + } + } + }, + "Inbound Type" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "入站类型" + } + } + } + }, + "includeAllNetworks" : { + "shouldTranslate" : false + }, + "Install Network Extension" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装网络拓展" + } + } + } + }, + "Install System Extension" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装系统拓展" + } + } + } + }, + "IP Version" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IP 版本" + } + } + } + }, + "Keep Menu Bar in Background" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将菜单栏保留在后台" + } + } + } + }, + "Last Updated" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后更新" + } + } + } + }, + "Last Updated: %@" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后更新:%@" + } + } + } + }, + "Launch the application when the system is logged in. If enabled at the same time as `Show in Menu Bar` and `Keep Menu Bar in Background`, the application interface will not be opened automatically." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在系统登录时启动应用,若同时开启 `在菜单栏中显示` 和 `将菜单栏保留在后台`,则不会自动打开应用界面。" + } + } + } + }, + "Loading..." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "加载中 . . ." + } + } + } + }, + "Local" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "本地" + } + } + } + }, + "Logs" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志" + } + } + } + }, + "Machine: " : { + "shouldTranslate" : false + }, + "Match Rule" : { + "shouldTranslate" : false + }, + "Memory" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内存" + } + } + } + }, + "Metadata" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "元数据" + } + } + } + }, + "Missing path" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缺少路径" + } + } + } + }, + "Missing profile name" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缺少配置文件名称" + } + } + } + }, + "Missing URL" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缺少 URL" + } + } + } + }, + "Name" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称" + } + } + } + }, + "Need Reboot" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要重启系统" + } + } + } + }, + "Network" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网络" + } + } + } + }, + "NetworkExtension not installed" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网络拓展未安装" + } + } + } + }, + "New Profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建配置" + } + } + } + }, + "No Default Route" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "不使用默认路由" + } + } + } + }, + "Ok" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "好" + } + } + } + }, + "On Demand Rules" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按需启动" + } + } + } + }, + "Open" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开" + } + } + } + }, + "Other methods" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他方式" + } + } + } + }, + "Outbound" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "出站" + } + } + } + }, + "Outbound Type" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "出站类型" + } + } + } + }, + "Overview" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "概述" + } + } + } + }, + "Packet Tunnel" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隧道" + } + } + } + }, + "Page" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "页面" + } + } + }, + "shouldTranslate" : false + }, + "Path" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "路径" + } + } + } + }, + "Please grant the permission for **SFMExtension**, then we can continue." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "请为 **SFMExtension** 授予权限,然后我们可以继续。" + } + } + } + }, + "Profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" + } + } + } + }, + "Profile Override" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置重载" + } + } + } + }, + "Profiles" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" + } + } + } + }, + "Protocol" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "协议" + } + } + } + }, + "Quit" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出" + } + } + } + }, + "Quit sing-box" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出 sing-box" + } + } + } + }, + "Rate on the App Store" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 App Store 上评分" + } + } + } + }, + "Reboot required." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要重启系统。" + } + } + } + }, + "Releases" : { + "shouldTranslate" : false + }, + "Remote" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "远程" + } + } + } + }, + "Required" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "必填" + } + } + } + }, + "Reset" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置" + } + } + } + }, + "Save" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + } + } + }, + "Service Error" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务错误" + } + } + } + }, + "Service Log" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务日志" + } + } + } + }, + "Service not started" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务未启动" + } + } + } + }, + "Settings" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" + } + } + } + }, + "Share" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享" + } + } + } + }, + "Share profile" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享配置" + } + } + } + }, + "Share URL as QR Code" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过二维码分享 URL" + } + } + } + }, + "Show in Menu Bar" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在菜单栏中显示" + } + } + } + }, + "sing-box" : { + "shouldTranslate" : false + }, + "Sort By" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "排序" + } + } + } + }, + "Source" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "源" + } + } + } + }, + "Source Code" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "源代码" + } + } + } + }, + "Sponsors" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "赞助" + } + } + } + }, + "Start" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动" + } + } + } + }, + "Start At Login" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开机启动" + } + } + } + }, + "State" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "状态" + } + } + } + }, + "Status" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "状态" + } + } + } + }, + "Stop" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止" + } + } + } + }, + "System Extension" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统拓展" + } + } + } + }, + "System Extension removed." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统扩展已移除。" + } + } + } + }, + "System Extension updated." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统扩展已更新。" + } + } + } + }, + "System: " : { + "shouldTranslate" : false + }, + "Taiwan Flag Available" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "特殊旗帜可用" + } + } + } + }, + "This app needs to be placed under the Applications folder to work." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此应用程序需要放置在 Applications 文件夹下才能运行。" + } + } + } + }, + "Traffic" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "速率" + } + } + } + }, + "Traffic Total" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "流量" + } + } + } + }, + "true" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "是" + } + } + } + }, + "Type" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "类型" + } + } + } + }, + "Uninstall" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "卸载" + } + } + } + }, + "Update" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" + } + } + } + }, + "Uplink" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上传" + } + } + } + }, + "URL" : { + "shouldTranslate" : false + }, + "User" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用户" + } + } + } + }, + "Version" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本" + } + } + } + }, + "View Content" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看内容" + } + } + } + }, + "Working Directory" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作目录" + } + } + } + }, + "Wrong application location" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用程序位置错误" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/MacLibrary/ApplicationDelegate.swift b/MacLibrary/ApplicationDelegate.swift index 078bc0d..208ee6f 100644 --- a/MacLibrary/ApplicationDelegate.swift +++ b/MacLibrary/ApplicationDelegate.swift @@ -9,6 +9,7 @@ open class ApplicationDelegate: NSObject, NSApplicationDelegate, UNUserNotificat public func applicationDidFinishLaunching(_: Notification) { NSLog("Here I stand") LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, false) + LibboxSetLocale(Locale.current.identifier) let notificationCenter = UNUserNotificationCenter.current() notificationCenter.setNotificationCategories([ UNNotificationCategory( diff --git a/MacLibrary/MainView.swift b/MacLibrary/MainView.swift index 687d3c8..7e5d914 100644 --- a/MacLibrary/MainView.swift +++ b/MacLibrary/MainView.swift @@ -78,7 +78,7 @@ public struct MainView: View { await importURLProfile(url) } } else { - alert = Alert(errorMessage: "Handled unknown URL \(url.absoluteString)") + alert = Alert(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)")) } } @@ -105,7 +105,7 @@ public struct MainView: View { if directoryName != "Applications" { alert = Alert( title: Text("Wrong application location"), - message: Text("This app needs to be placed under ~/Applications to work."), + message: Text("This app needs to be placed under the Applications folder to work."), dismissButton: .default(Text("Ok")) { NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: "") NSApp.terminate(nil) diff --git a/MacLibrary/MenuView.swift b/MacLibrary/MenuView.swift index ea9764d..f578dac 100644 --- a/MacLibrary/MenuView.swift +++ b/MacLibrary/MenuView.swift @@ -140,7 +140,7 @@ public struct MenuView: View { if profileList.isEmpty { Text("Empty profiles") } else { - MenuSection("Profile") + MenuSection(String(localized: "Profile")) Picker("", selection: selectedProfileIDLocal) { ForEach(profileList, id: \.id) { profile in Text(profile.name) diff --git a/SFI/ApplicationDelegate.swift b/SFI/ApplicationDelegate.swift index 6ab06c7..b5582ab 100644 --- a/SFI/ApplicationDelegate.swift +++ b/SFI/ApplicationDelegate.swift @@ -12,6 +12,7 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCe func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { NSLog("Here I stand") LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, false) + LibboxSetLocale(Locale.current.identifier) let notificationCenter = UNUserNotificationCenter.current() notificationCenter.setNotificationCategories([ UNNotificationCategory( diff --git a/SFI/MainView.swift b/SFI/MainView.swift index b854a86..9b38454 100644 --- a/SFI/MainView.swift +++ b/SFI/MainView.swift @@ -76,7 +76,7 @@ struct MainView: View { selection = .profiles } } else { - alert = Alert(errorMessage: "Handled unknown URL \(url.absoluteString)") + alert = Alert(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)")) } } } diff --git a/SFT/ApplicationDelegate.swift b/SFT/ApplicationDelegate.swift index 2c1fc83..3075a8f 100644 --- a/SFT/ApplicationDelegate.swift +++ b/SFT/ApplicationDelegate.swift @@ -8,6 +8,7 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { NSLog("Here I stand") LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, true) + LibboxSetLocale(Locale.current.identifier) setup() return true } diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index cd828c8..1d9569e 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -84,6 +84,7 @@ 3A7701702A4E6B34008F031F /* IntentsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A77016F2A4E6B34008F031F /* IntentsExtension.swift */; }; 3A7701722A4E6B34008F031F /* Intents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7701712A4E6B34008F031F /* Intents.swift */; }; 3A7904502B6E7BAC006C08D5 /* SponsorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A79044F2B6E7BAC006C08D5 /* SponsorsView.swift */; }; + 3A7C8B452D0C81E10070EE55 /* StringCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7C8B442D0C81DC0070EE55 /* StringCompat.swift */; }; 3A7E90352A46756300D53052 /* SharedPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7E90342A46756300D53052 /* SharedPreferences.swift */; }; 3A7E90382A46778E00D53052 /* BinaryCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 3A7E90372A46778E00D53052 /* BinaryCodable */; }; 3A8655142A4FA26600B7181F /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -164,6 +165,13 @@ 3AF342A02A4A9916002B34AC /* ExtensionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF3429F2A4A9916002B34AC /* ExtensionProfile.swift */; }; 3AF3A3D22B2207F3001FD7C1 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */; }; 3AF5E3E72B6F90640058B9E8 /* ProfileOverrideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF5E3E62B6F90640058B9E8 /* ProfileOverrideView.swift */; }; + 9C37F17D2C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F17E2C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F17F2C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F1812C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F1822C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F1832C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; + 9C37F1842C131100001B9FA8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -521,6 +529,7 @@ 3A7701732A4E6B34008F031F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3A7701802A4E71F5008F031F /* IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentsExtension.entitlements; sourceTree = ""; }; 3A79044F2B6E7BAC006C08D5 /* SponsorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorsView.swift; sourceTree = ""; }; + 3A7C8B442D0C81DC0070EE55 /* StringCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCompat.swift; sourceTree = ""; }; 3A7E90302A46745A00D53052 /* ViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewBuilder.swift; sourceTree = ""; }; 3A7E90342A46756300D53052 /* SharedPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedPreferences.swift; sourceTree = ""; }; 3A9144D82A46AE370036E9AD /* ShadredPreferences+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShadredPreferences+Database.swift"; sourceTree = ""; }; @@ -613,6 +622,7 @@ 3AF342D32A4AADB2002B34AC /* FormItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormItem.swift; sourceTree = ""; }; 3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libresolv.tbd; sourceTree = DEVELOPER_DIR; }; 3AF5E3E62B6F90640058B9E8 /* ProfileOverrideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileOverrideView.swift; sourceTree = ""; }; + 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -886,6 +896,7 @@ 3AEC20BC2A45991900A63465 = { isa = PBXGroup; children = ( + 9C37F17C2C131100001B9FA8 /* Localizable.xcstrings */, 3AEC20DB2A4599D000A63465 /* Libbox.xcframework */, 3AEC20F42A459AB400A63465 /* SFI */, 3AEC210A2A459B1900A63465 /* SFM */, @@ -1094,6 +1105,7 @@ 3AF342D22A4AADA5002B34AC /* Abstract */ = { isa = PBXGroup; children = ( + 3A7C8B442D0C81DC0070EE55 /* StringCompat.swift */, 3A7E90302A46745A00D53052 /* ViewBuilder.swift */, 3AF342D32A4AADB2002B34AC /* FormItem.swift */, 3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */, @@ -1487,6 +1499,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C37F1832C131100001B9FA8 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1494,6 +1507,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C37F1812C131100001B9FA8 /* Localizable.xcstrings in Resources */, 3AC03B9D2A72BF3500B7946F /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1519,6 +1533,7 @@ files = ( 3A6CA5A92A713C420027933B /* AppIcon.icns in Resources */, 3AABFD432A9CC5A7005A24A4 /* Upload.plist in Resources */, + 9C37F17D2C131100001B9FA8 /* Localizable.xcstrings in Resources */, 3AEC20FA2A459AB500A63465 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1529,6 +1544,7 @@ files = ( 3A6CA5A72A713ABA0027933B /* AppIcon.icns in Resources */, 3A6CA5A32A713A580027933B /* Assets.xcassets in Resources */, + 9C37F17E2C131100001B9FA8 /* Localizable.xcstrings in Resources */, 3A251C122A52D09700651082 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1537,6 +1553,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C37F1822C131100001B9FA8 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1552,6 +1569,7 @@ buildActionMask = 2147483647; files = ( 3AABFD472A9CCC58005A24A4 /* Upload.plist in Resources */, + 9C37F17F2C131100001B9FA8 /* Localizable.xcstrings in Resources */, 3A6CA5A82A713B340027933B /* AppIcon.icns in Resources */, 3AEC8A4F2A6E5E18003702E1 /* Assets.xcassets in Resources */, 3A2F29EB2C998A5D007E024C /* Export.plist in Resources */, @@ -1563,6 +1581,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C37F1842C131100001B9FA8 /* Localizable.xcstrings in Resources */, 3A6CA5A62A713AA10027933B /* AppIcon.icns in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1626,6 +1645,7 @@ 3ACE6DE32ACADF55009D9A8A /* Binding+Setter.swift in Sources */, 3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */, 3A63269E2C0DE12D0076E274 /* ConnectionListView.swift in Sources */, + 3A7C8B452D0C81E10070EE55 /* StringCompat.swift in Sources */, 3A4EAD252A4FEB65005435B3 /* StartStopButton.swift in Sources */, 3A6326A02C0DE15C0076E274 /* Connection.swift in Sources */, 3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */,