fix(i18n): 修复首选语言回退并准备 v1.0.8

This commit is contained in:
xweiba
2026-09-24 23:19:21 +08:00
parent 228b67b210
commit 5b9fedfe9e
34 changed files with 1640 additions and 173 deletions
+33
View File
@@ -0,0 +1,33 @@
import Foundation
/// Chooses the app language from the first preferred language only. iOS normally
/// scans the whole preference list, which can select Chinese when Italian is first
/// and Chinese is second; this app intentionally falls back to English instead.
enum AppLocalization {
static func identifier(for preferredLanguages: [String]) -> String {
guard let first = preferredLanguages.first,
Locale(identifier: first).languageCode == "zh" else {
return "en"
}
return Bundle.preferredLocalizations(
from: ["zh-Hans", "zh-Hant"], forPreferences: [first]
).first ?? "zh-Hans"
}
static let identifier = identifier(for: Locale.preferredLanguages)
static let locale = Locale(identifier: identifier)
// An explicit .lproj bundle also covers String(localized:) calls made outside
// SwiftUI views, where the view's locale environment is not available.
private static let localizedBundle: Bundle = {
guard let path = Bundle.main.path(forResource: identifier, ofType: "lproj"),
let bundle = Bundle(path: path) else {
return .main
}
return bundle
}()
static func string(_ value: String.LocalizationValue) -> String {
String(localized: value, bundle: localizedBundle)
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ struct AppRemoteConfiguration: Decodable, Equatable {
let communityPromptClients: [String]
static let fallback = AppRemoteConfiguration(
latestVersion: "1.0.7",
latestVersion: "1.0.8",
minimumSupportedVersion: "1.0.0",
communityPromptClients: [
ThirdPartyProxyClient.surge.rawValue,
+2 -2
View File
@@ -13,8 +13,8 @@ enum CertificateAuthorityStoreError: LocalizedError {
var errorDescription: String? {
switch self {
case .invalidAuthority: return String(localized: "本地 CA 证书或私钥无效")
case let .keychain(status): return String(localized: "无法写入设备钥匙串(\(status))")
case .invalidAuthority: return AppLocalization.string("本地 CA 证书或私钥无效")
case let .keychain(status): return AppLocalization.string("无法写入设备钥匙串(\(status))")
}
}
}
+2 -2
View File
@@ -12,8 +12,8 @@ enum CoreBridgeError: LocalizedError {
var errorDescription: String? {
switch self {
case .generationFailed: return String(localized: "无法生成本地证书")
case .serverStartFailed: return String(localized: "无法启动本地证书服务")
case .generationFailed: return AppLocalization.string("无法生成本地证书")
case .serverStartFailed: return AppLocalization.string("无法启动本地证书服务")
}
}
}
+2 -2
View File
@@ -8,8 +8,8 @@ enum ProxyRuntimeMode: String, CaseIterable, Codable, Identifiable {
var displayName: String {
switch self {
case .localWiFi: return String(localized: "APP模式")
case .thirdParty: return String(localized: "第三方代理模式")
case .localWiFi: return AppLocalization.string("APP模式")
case .thirdParty: return AppLocalization.string("第三方代理模式")
}
}
}
+3 -3
View File
@@ -58,13 +58,13 @@ struct RuntimeLogEntry: Codable, Identifiable, Equatable {
}
private func localizedDiagnosticText(_ text: String) -> String {
String(localized: String.LocalizationValue(text))
AppLocalization.string(String.LocalizationValue(text))
}
private func localizedDiagnosticValue(_ value: String) -> String {
switch value {
case "true": return String(localized: "是")
case "false": return String(localized: "否")
case "true": return AppLocalization.string("是")
case "false": return AppLocalization.string("否")
default: return localizedDiagnosticText(value)
}
}
+11 -11
View File
@@ -25,25 +25,25 @@ enum ThirdPartyProxyError: LocalizedError, Equatable {
var errorDescription: String? {
switch self {
case .invalidResponse:
return String(localized: "第三方代理返回了无法识别的数据")
return AppLocalization.string("第三方代理返回了无法识别的数据")
case .moduleNotIntercepted:
return String(localized: "请求未被第三方代理模块拦截,请检查模块、MITM 和代理连接")
return AppLocalization.string("请求未被第三方代理模块拦截,请检查模块、MITM 和代理连接")
case .rejected(let message):
return message
case .coordinateMismatch:
return String(localized: "第三方代理保存的坐标与当前选点不一致")
return AppLocalization.string("第三方代理保存的坐标与当前选点不一致")
case .network(let message):
return String(localized: "第三方代理请求失败:\(message)")
return AppLocalization.string("第三方代理请求失败:\(message)")
}
}
var recoverySuggestion: String {
return String(localized: "检查模块、MITM、证书和代理/VPN连接")
return AppLocalization.string("检查模块、MITM、证书和代理/VPN连接")
}
static func recoverySuggestion(for error: Error) -> String {
(error as? Self)?.recoverySuggestion
?? String(localized: "检查模块、MITM、证书和代理/VPN连接")
?? AppLocalization.string("检查模块、MITM、证书和代理/VPN连接")
}
}
@@ -105,7 +105,7 @@ final class ThirdPartyProxyManager: ObservableObject {
randomRadius: RandomRadiusStore.shared.isEnabled ? RandomRadiusStore.shared.radius : 0
))
guard response.success else {
throw ThirdPartyProxyError.rejected(response.error ?? String(localized: "第三方代理拒绝保存坐标"))
throw ThirdPartyProxyError.rejected(response.error ?? AppLocalization.string("第三方代理拒绝保存坐标"))
}
guard let latitude = response.latitude,
let longitude = response.longitude,
@@ -126,7 +126,7 @@ final class ThirdPartyProxyManager: ObservableObject {
func clear() async throws {
let response = try await perform(action: .clear)
guard response.success else {
throw ThirdPartyProxyError.rejected(response.error ?? String(localized: "第三方代理清除坐标失败"))
throw ThirdPartyProxyError.rejected(response.error ?? AppLocalization.string("第三方代理清除坐标失败"))
}
activeSettings = nil
connectionState = .connected(active: false)
@@ -142,7 +142,7 @@ final class ThirdPartyProxyManager: ObservableObject {
if response.error?.contains("无已保存") == true {
return false
}
throw ThirdPartyProxyError.rejected(response.error ?? String(localized: "第三方代理查询失败"))
throw ThirdPartyProxyError.rejected(response.error ?? AppLocalization.string("第三方代理查询失败"))
}
private enum Action {
@@ -153,7 +153,7 @@ final class ThirdPartyProxyManager: ObservableObject {
private func perform(action: Action) async throws -> ThirdPartyProxySettingsResponse {
guard !isRequesting else {
throw ThirdPartyProxyError.rejected(String(localized: "已有第三方代理请求正在执行"))
throw ThirdPartyProxyError.rejected(AppLocalization.string("已有第三方代理请求正在执行"))
}
isRequesting = true
defer { isRequesting = false }
@@ -229,7 +229,7 @@ enum ThirdPartyProxyClient: String, CaseIterable, Identifiable {
}
var verificationText: String? {
self == .shadowrocket ? nil : String(localized: "配置已提供,尚未验证")
self == .shadowrocket ? nil : AppLocalization.string("配置已提供,尚未验证")
}
var moduleFileName: String {
+8 -8
View File
@@ -29,14 +29,14 @@ enum VerificationResult: Equatable, Identifiable {
/// Localized, user-facing title. `id` stays stable for routing/equality.
var localizedTitle: String {
switch self {
case .success: return String(localized: "成功")
case .proxyNotRunning: return String(localized: "代理未运行")
case .verificationInProgress: return String(localized: "已有验证正在进行")
case .verificationSuperseded: return String(localized: "验证已被新位置取代")
case .certNotTrusted: return String(localized: "证书未信任")
case .wifiProxyNotConfigured: return String(localized: "WiFi代理未配置")
case .coordinateWriteFailed: return String(localized: "坐标写入失败")
case .patchFailed: return String(localized: "改写验证失败")
case .success: return AppLocalization.string("成功")
case .proxyNotRunning: return AppLocalization.string("代理未运行")
case .verificationInProgress: return AppLocalization.string("已有验证正在进行")
case .verificationSuperseded: return AppLocalization.string("验证已被新位置取代")
case .certNotTrusted: return AppLocalization.string("证书未信任")
case .wifiProxyNotConfigured: return AppLocalization.string("WiFi代理未配置")
case .coordinateWriteFailed: return AppLocalization.string("坐标写入失败")
case .patchFailed: return AppLocalization.string("改写验证失败")
}
}