Add English localization (device-language based)

Add English as an overlay localization following the device language,
keeping Simplified Chinese as the base/development language. Untranslated
strings fall back to Chinese automatically.

- project.yml: set developmentLanguage zh-Hans and knownRegions (zh-Hans/en/Base)
- Resources/en.lproj/{Localizable,InfoPlist}.strings: ~230 English strings
- Resources/zh-Hans.lproj/InfoPlist.strings: Chinese permission prompts
- Restructure ternary Text/Label and String-typed UI messages so they
  localize (String(localized:), LocalizedStringKey helper params); replace
  a .contains("通过") success check with a dedicated flag
- Localize user-facing model/coordinator/error messages across App and Shared

Internal RuntimeLogger logs and diagnostic/report bodies remain in Chinese.
This commit is contained in:
Syntax-Error-1337
2026-09-10 01:05:45 +08:00
parent eadeca636d
commit 908ac8f734
17 changed files with 493 additions and 121 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ struct BugReportView: View {
if isRunning {
ProgressView().tint(.white)
}
Text(isRunning ? "正在生成报告…" : "生成 Bug 报告")
(isRunning ? Text("正在生成报告…") : Text("生成 Bug 报告"))
.font(.body.weight(.medium))
}
.frame(maxWidth: .infinity)
+5 -5
View File
@@ -20,9 +20,9 @@ struct ContentView: View {
Image(systemName: "location.fill")
.font(.system(size: 48)).foregroundStyle(.blue)
ProgressView()
Text(runtimeMode.hasSelectedMode && runtimeMode.mode == .localWiFi
? "正在初始化地图与本地代理…"
: "正在初始化地图…")
(runtimeMode.hasSelectedMode && runtimeMode.mode == .localWiFi
? Text("正在初始化地图与本地代理…")
: Text("正在初始化地图…"))
.font(.subheadline).foregroundStyle(.secondary)
}
case .setup:
@@ -177,7 +177,7 @@ struct ContentView: View {
switch prompt.requirement {
case .required:
let details = prompt.releaseNotes
?? "更新说明暂时无法加载,请前往最新 Release 页面查看。"
?? String(localized: "更新说明暂时无法加载,请前往最新 Release 页面查看。")
return Alert(
title: Text("需要更新"),
message: Text(
@@ -189,7 +189,7 @@ struct ContentView: View {
)
case .recommended:
let details = prompt.releaseNotes
?? "更新说明暂时无法加载,请前往最新 Release 页面查看。"
?? String(localized: "更新说明暂时无法加载,请前往最新 Release 页面查看。")
return Alert(
title: Text("发现新版本"),
message: Text(
+18 -12
View File
@@ -12,6 +12,7 @@ struct RuntimeLogsView: View {
@State private var entries: [RuntimeLogEntry] = []
@State private var isTesting = false
@State private var testResult = ""
@State private var testSucceeded = false
@State private var testMessage = ""
@State private var showClearConfirm = false
@State private var copiedEntryID: UUID?
@@ -42,7 +43,7 @@ struct RuntimeLogsView: View {
if filteredEntries.isEmpty {
VStack(spacing: 10) {
Image(systemName: "doc.text.magnifyingglass").font(.largeTitle).foregroundStyle(.secondary)
Text(entries.isEmpty ? "暂无运行日志" : "无匹配日志").foregroundStyle(.secondary)
(entries.isEmpty ? Text("暂无运行日志") : Text("无匹配日志")).foregroundStyle(.secondary)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
ScrollView {
@@ -103,8 +104,11 @@ struct RuntimeLogsView: View {
await runThirdPartyConnectionTest()
} else {
let result = await setup.runVerificationTest()
testResult = result.isSuccess ? "环境检测通过" : "环境检测失败: \(result.id)"
if !result.isSuccess { testResult += ",查看下方日志" }
testSucceeded = result.isSuccess
testResult = result.isSuccess
? String(localized: "环境检测通过")
: String(localized: "环境检测失败: \(result.localizedTitle)")
if !result.isSuccess { testResult += String(localized: ",查看下方日志") }
testMessage = setup.testLog
}
isTesting = false; refresh()
@@ -116,7 +120,7 @@ struct RuntimeLogsView: View {
} else {
Image(systemName: "play.fill").font(.system(size: 13, weight: .bold))
}
Text(isTesting ? "正在检测…" : "环境检测").font(.subheadline.weight(.semibold))
(isTesting ? Text("正在检测…") : Text("环境检测")).font(.subheadline.weight(.semibold))
Spacer()
Image(systemName: "chevron.right").font(.system(size: 12, weight: .semibold)).opacity(0.5)
}
@@ -131,9 +135,9 @@ struct RuntimeLogsView: View {
.fill(isTesting ? Color.gray : Color.blue)
)
.disabled(isTesting || actions.state.isBusy)
Text(runtimeMode.mode == .thirdParty
? "检查第三方模块能否拦截并响应 query 请求;不会写入测试坐标。"
: "依次检查:本地代理 → CA 证书信任 → Wi-Fi 代理链路。")
(runtimeMode.mode == .thirdParty
? Text("检查第三方模块能否拦截并响应 query 请求;不会写入测试坐标。")
: Text("依次检查:本地代理 → CA 证书信任 → Wi-Fi 代理链路。"))
.font(.caption).foregroundStyle(.secondary)
if !testMessage.isEmpty {
VStack(alignment: .leading, spacing: 6) {
@@ -180,13 +184,13 @@ struct RuntimeLogsView: View {
}
if runtimeMode.mode == .localWiFi {
HStack(spacing: 14) {
Label(proxy.isRunning ? "代理运行中" : "代理未运行", systemImage: proxy.isRunning ? "play.circle" : "stop.circle")
Label(setup.canModify ? "可修改" : "不可修改", systemImage: setup.canModify ? "checkmark.shield.fill" : "xmark.shield")
(proxy.isRunning ? Label("代理运行中", systemImage: "play.circle") : Label("代理未运行", systemImage: "stop.circle"))
(setup.canModify ? Label("可修改", systemImage: "checkmark.shield.fill") : Label("不可修改", systemImage: "xmark.shield"))
}.font(.caption).foregroundStyle(.secondary)
}
if !testResult.isEmpty {
Text(testResult).font(.footnote.weight(.medium))
.foregroundStyle(testResult.contains("通过") ? .green : .red)
.foregroundStyle(testSucceeded ? .green : .red)
}
}.padding(14).background(Color(.secondarySystemBackground))
}
@@ -240,7 +244,8 @@ struct RuntimeLogsView: View {
do {
let response = try await thirdPartyProxy.query()
let active = response.success && response.latitude != nil && response.longitude != nil
testResult = active ? "第三方模块连接通过,已有坐标" : "第三方模块连接通过,暂无坐标"
testSucceeded = true
testResult = active ? String(localized: "第三方模块连接通过,已有坐标") : String(localized: "第三方模块连接通过,暂无坐标")
testMessage = """
======== 第三方代理连接检测 ========
模式: 测试模式
@@ -249,7 +254,8 @@ struct RuntimeLogsView: View {
已保存坐标: \(active ? "是" : "否")
"""
} catch {
testResult = "第三方模块连接失败"
testSucceeded = false
testResult = String(localized: "第三方模块连接失败")
testMessage = """
======== 第三方代理连接检测 ========
模式: 测试模式
+31 -33
View File
@@ -150,7 +150,7 @@ struct FirstSetupView: View {
.clipShape(RoundedRectangle(cornerRadius: 8))
.padding()
}
.navigationTitle(preview.title)
.navigationTitle(LocalizedStringKey(preview.title))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
@@ -188,7 +188,7 @@ struct FirstSetupView: View {
Circle()
.fill(value.rawValue <= step.rawValue ? Color.blue : Color.gray.opacity(0.3))
.frame(width: 10, height: 10)
Text(value.title).font(.caption).foregroundStyle(.secondary)
Text(LocalizedStringKey(value.title)).font(.caption).foregroundStyle(.secondary)
}
if value != visibleSteps.last {
Rectangle().fill(Color.gray.opacity(0.3)).frame(width: 28, height: 2)
@@ -274,10 +274,10 @@ struct FirstSetupView: View {
}
private func modeCard(
title: String,
title: LocalizedStringKey,
icon: String,
badges: [String],
description: String,
description: LocalizedStringKey,
tint: Color,
action: @escaping () -> Void
) -> some View {
@@ -288,7 +288,7 @@ struct FirstSetupView: View {
.foregroundStyle(tint)
HStack(spacing: 6) {
ForEach(badges, id: \.self) { badge in
Text(badge)
Text(LocalizedStringKey(badge))
.font(.caption2.weight(.semibold))
.padding(.horizontal, 8)
.padding(.vertical, 4)
@@ -354,7 +354,7 @@ struct FirstSetupView: View {
if let url = await setup.proxy.prepareCertificateDownloadURL() {
certificateDownloadDestination = CertificateDownloadDestination(url: url)
} else {
setupActionError = setup.proxy.error ?? "无法准备证书下载页面,请查看诊断日志"
setupActionError = setup.proxy.error ?? String(localized: "无法准备证书下载页面,请查看诊断日志")
}
}
},
@@ -507,7 +507,7 @@ struct FirstSetupView: View {
UIPasteboard.general.string = client.subscriptionURL.absoluteString
copiedSubscriptionURL = true
} label: {
Label(copiedSubscriptionURL ? "已复制模块订阅地址" : "复制模块订阅地址", systemImage: "doc.on.doc")
(copiedSubscriptionURL ? Label("已复制模块订阅地址", systemImage: "doc.on.doc") : Label("复制模块订阅地址", systemImage: "doc.on.doc"))
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
@@ -565,7 +565,7 @@ struct FirstSetupView: View {
if let thirdPartyFailureLog {
testResultView(
success: false,
title: "接口连接失败",
title: String(localized: "接口连接失败"),
log: thirdPartyFailureLog
)
.id("thirdPartyFailureLog")
@@ -615,16 +615,15 @@ struct FirstSetupView: View {
UIPasteboard.general.string = ThirdPartyProxyManager.interceptionHostnamesText
copiedMITMHostname = true
} label: {
Label(
copiedMITMHostname ? "已复制解密域名" : "复制解密域名",
systemImage: "doc.on.doc"
)
(copiedMITMHostname
? Label("已复制解密域名", systemImage: "doc.on.doc")
: Label("复制解密域名", systemImage: "doc.on.doc"))
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
}
private func instructionRow(_ number: Int, _ text: String) -> some View {
private func instructionRow(_ number: Int, _ text: LocalizedStringKey) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(number)")
.font(.caption2.bold())
@@ -642,7 +641,7 @@ struct FirstSetupView: View {
private func setupScreenshot(
assetName: String,
title: String,
caption: String
caption: LocalizedStringKey
) -> some View {
if let image = UIImage(named: assetName) {
Button {
@@ -673,7 +672,7 @@ struct FirstSetupView: View {
)
}
.buttonStyle(.plain)
.accessibilityLabel("\(title):\(caption)")
.accessibilityLabel(LocalizedStringKey(title))
.accessibilityHint("轻点查看大图")
}
}
@@ -683,7 +682,7 @@ struct FirstSetupView: View {
UIApplication.shared.open(url, options: [:]) { opened in
guard !opened else { return }
Task { @MainActor in
manualHint = "无法打开 \(client.name),请确认客户端已安装后手动打开。"
manualHint = String(localized: "无法打开 \(client.name),请确认客户端已安装后手动打开。")
}
}
}
@@ -705,10 +704,10 @@ struct FirstSetupView: View {
}
private func certificateCard(
title: String,
title: LocalizedStringKey,
icon: String,
description: String,
actionTitle: String,
description: LocalizedStringKey,
actionTitle: LocalizedStringKey,
actionIcon: String,
complete: Bool,
action: @escaping () -> Void,
@@ -728,10 +727,9 @@ struct FirstSetupView: View {
.buttonStyle(.borderedProminent)
.tint(.blue)
Button(action: markComplete) {
Label(
complete ? "已完成 ✓" : "已完成",
systemImage: complete ? "checkmark.circle.fill" : "circle"
)
(complete
? Label("已完成 ✓", systemImage: "checkmark.circle.fill")
: Label("已完成", systemImage: "circle"))
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
@@ -746,7 +744,7 @@ struct FirstSetupView: View {
let success = result.isSuccess
testResultView(
success: success,
title: success ? "环境检测通过" : failureSummary(result),
title: success ? String(localized: "环境检测通过") : failureSummary(result),
log: setup.testLog
)
}
@@ -905,7 +903,7 @@ struct FirstSetupView: View {
}
}
private func actionLabel(_ title: String) -> some View {
private func actionLabel(_ title: LocalizedStringKey) -> some View {
HStack {
if isVerifying { ProgressView().tint(.white).controlSize(.small) }
Text(title)
@@ -954,14 +952,14 @@ struct FirstSetupView: View {
private func failureSummary(_ result: VerificationResult) -> String {
switch result {
case .certNotTrusted: return "证书尚未安装或信任"
case .wifiProxyNotConfigured: return "Wi-Fi 代理未正确设置"
case .proxyNotRunning: return "本地代理未能启动"
case .verificationInProgress: return "检测仍在进行"
case .verificationSuperseded: return "检测结果已过期"
case .coordinateWriteFailed: return "坐标写入失败"
case .patchFailed: return "定位改写检测失败"
case .success: return "环境检测通过"
case .certNotTrusted: return String(localized: "证书尚未安装或信任")
case .wifiProxyNotConfigured: return String(localized: "Wi-Fi 代理未正确设置")
case .proxyNotRunning: return String(localized: "本地代理未能启动")
case .verificationInProgress: return String(localized: "检测仍在进行")
case .verificationSuperseded: return String(localized: "检测结果已过期")
case .coordinateWriteFailed: return String(localized: "坐标写入失败")
case .patchFailed: return String(localized: "定位改写检测失败")
case .success: return String(localized: "环境检测通过")
}
}
+16 -16
View File
@@ -301,7 +301,7 @@ struct MapHomeView: View {
}
} message: {
Text(
"你正在使用 \(communityContributionClient?.name ?? "第三方客户端")。点击“去提交”会先复制投稿模板,并在 App 内打开社区页面。采纳后将收录到 README,可选择是否匿名署名。"
"你正在使用 \(communityContributionClient?.name ?? String(localized: "第三方客户端"))。点击“去提交”会先复制投稿模板,并在 App 内打开社区页面。采纳后将收录到 README,可选择是否匿名署名。"
)
}
.alert("已复制投稿模板", isPresented: $showCommunityTemplateCopied) {
@@ -483,7 +483,7 @@ struct MapHomeView: View {
// 当前选点
HStack {
VStack(alignment: .leading, spacing: 3) {
Text(mapState.displayName ?? "当前选点").font(.subheadline.weight(.semibold)).lineLimit(1)
Text(mapState.displayName ?? String(localized: "当前选点")).font(.subheadline.weight(.semibold)).lineLimit(1)
coordinateRow(label: "GCJ-02(国内)", system: .gcj02)
coordinateRow(label: "WGS-84(国际)", system: .wgs84)
}
@@ -496,7 +496,7 @@ struct MapHomeView: View {
activeTip = .deactivation
}
} label: {
Text(spoofState == .active ? "无法生效?" : "无法取消?")
(spoofState == .active ? Text("无法生效?") : Text("无法取消?"))
.font(.system(size: 10, weight: .medium))
.foregroundStyle(.secondary)
.padding(.horizontal, 8)
@@ -520,7 +520,7 @@ struct MapHomeView: View {
.buttonStyle(.plain)
.foregroundStyle(favorites.selectedFavoriteID != nil ? .orange : .gray)
.disabled(favoriteSaveTask != nil)
.accessibilityLabel(favorites.selectedFavoriteID != nil ? "已收藏,点击取消收藏" : "收藏当前选点")
.accessibilityLabel(favorites.selectedFavoriteID != nil ? Text("已收藏,点击取消收藏") : Text("收藏当前选点"))
}
// 收藏
if favorites.favorites.isEmpty {
@@ -537,7 +537,7 @@ struct MapHomeView: View {
if spoofState == .verifying {
ProgressView().tint(.white)
}
Text(spoofState == .active && needsSwitchButton ? "关闭" : buttonTitle)
(spoofState == .active && needsSwitchButton ? Text("关闭") : Text(buttonTitle))
.font(.headline).lineLimit(1)
}
.frame(maxWidth: needsSwitchButton ? nil : .infinity)
@@ -585,15 +585,15 @@ struct MapHomeView: View {
private var buttonTitle: String {
if runtimeMode.mode == .thirdParty {
switch spoofState {
case .idle: return "同步到第三方代理"
case .verifying: return "检测并同步中…"
case .active: return "停止第三方虚拟定位"
case .idle: return String(localized: "同步到第三方代理")
case .verifying: return String(localized: "检测并同步中…")
case .active: return String(localized: "停止第三方虚拟定位")
}
}
switch spoofState {
case .idle: return "开始虚拟定位"
case .verifying: return "验证环境中…"
case .active: return "停止虚拟定位"
case .idle: return String(localized: "开始虚拟定位")
case .verifying: return String(localized: "验证环境中…")
case .active: return String(localized: "停止虚拟定位")
}
}
@@ -856,7 +856,7 @@ struct MapHomeView: View {
}
private func coordinateRow(
label: String,
label: LocalizedStringKey,
system: CoordinateConverter.MapCoordinateSystem
) -> some View {
let coordinate = currentSelectionPair.coordinate(for: system)
@@ -1091,7 +1091,7 @@ struct MapHomeView: View {
"请求动作": "WLOC query",
"错误": response.error ?? "未知错误"
])
setup.requestThirdPartySetup(message: response.error ?? "第三方代理查询失败")
setup.requestThirdPartySetup(message: response.error ?? String(localized: "第三方代理查询失败"))
}
} catch {
spoofState = .idle
@@ -1144,7 +1144,7 @@ struct MapHomeView: View {
"Wi-Fi接口": String(net.isWiFiEnabled)
])
activeTip = nil
setup.requestSetup(message: "当前未连接可用的 Wi-Fi,请连接 Wi-Fi 后配置 127.0.0.1:8888 手动代理。")
setup.requestSetup(message: String(localized: "当前未连接可用的 Wi-Fi,请连接 Wi-Fi 后配置 127.0.0.1:8888 手动代理。"))
return
}
@@ -1517,7 +1517,7 @@ struct MapHomeView: View {
}
searchResults = (response?.mapItems ?? []).prefix(6).map { item in
let r = SearchLocationResult(
name: item.name ?? "未命名",
name: item.name ?? String(localized: "未命名"),
subtitle: [item.placemark.locality, item.placemark.subLocality, item.placemark.thoroughfare]
.compactMap { $0 }
.filter { !$0.isEmpty }
@@ -1529,7 +1529,7 @@ struct MapHomeView: View {
])
return r
}
if searchResults.isEmpty { searchError = "没有找到相关地点" }
if searchResults.isEmpty { searchError = String(localized: "没有找到相关地点") }
}
}
}
+2 -2
View File
@@ -154,13 +154,13 @@ final class ProxyManager: ObservableObject {
do {
if !isRunning { try await start() }
guard let url = URL(string: "http://127.0.0.1:8888/cert") else {
error = "证书下载地址无效"
error = String(localized: "证书下载地址无效")
return nil
}
error = nil
return url
} catch {
self.error = "启动代理失败: \(error.localizedDescription)"
self.error = String(localized: "启动代理失败: \(error.localizedDescription)")
RuntimeLogger.error("APP", "Certificate", "准备证书下载失败", error: error)
return nil
}
+26 -26
View File
@@ -30,7 +30,7 @@ struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
@State private var activeTip: TipKind?
@State private var proxyOperationError = ""
@State private var proxyOperationAlertTitle = "代理操作失败"
@State private var proxyOperationAlertTitle = String(localized: "代理操作失败")
@State private var modeOperationRunning = false
@State private var copiedClient: ThirdPartyProxyClient?
@State private var copiedMITMHostnames = false
@@ -268,7 +268,7 @@ struct SettingsView: View {
}
}
private func valueRow(_ title: String, value: String) -> some View {
private func valueRow(_ title: LocalizedStringKey, value: String) -> some View {
HStack { Text(title); Spacer(); Text(value).font(.footnote.monospaced()).foregroundStyle(.secondary) }
}
@@ -319,15 +319,15 @@ struct SettingsView: View {
)
case .available(let prompt):
let details = prompt.releaseNotes
?? "更新说明暂时无法加载,请前往最新 Release 页面查看。"
?? String(localized: "更新说明暂时无法加载,请前往最新 Release 页面查看。")
let message: String
if prompt.requirement == .required {
message = "当前版本 \(prompt.currentVersion) 已停止支持,请更新到 \(prompt.latestVersion) 后继续使用。\n\n\(details)"
message = String(localized: "当前版本 \(prompt.currentVersion) 已停止支持,请更新到 \(prompt.latestVersion) 后继续使用。\n\n\(details)")
} else {
message = "当前版本 \(prompt.currentVersion),最新版本 \(prompt.latestVersion)。\n\n\(details)"
message = String(localized: "当前版本 \(prompt.currentVersion),最新版本 \(prompt.latestVersion)。\n\n\(details)")
}
return Alert(
title: Text(prompt.requirement == .required ? "需要更新" : "发现新版本"),
title: prompt.requirement == .required ? Text("需要更新") : Text("发现新版本"),
message: Text(message),
primaryButton: .default(Text("前往更新")) {
UIApplication.shared.open(AppRemoteConfigurationService.releasesURL)
@@ -351,7 +351,7 @@ struct SettingsView: View {
try await proxy.start()
} catch {
proxy.error = error.localizedDescription
proxyOperationAlertTitle = "代理操作失败"
proxyOperationAlertTitle = String(localized: "代理操作失败")
proxyOperationError = error.localizedDescription
}
} else {
@@ -422,14 +422,14 @@ struct SettingsView: View {
UIPasteboard.general.string = thirdPartyClient.selectedClient.subscriptionURL.absoluteString
copiedClient = thirdPartyClient.selectedClient
} label: {
Label(copiedClient == thirdPartyClient.selectedClient ? "已复制模块订阅地址" : "复制模块订阅地址", systemImage: "doc.on.doc")
(copiedClient == thirdPartyClient.selectedClient ? Label("已复制模块订阅地址", systemImage: "doc.on.doc") : Label("复制模块订阅地址", systemImage: "doc.on.doc"))
}
Button {
UIPasteboard.general.string = ThirdPartyProxyManager.interceptionHostnamesText
copiedMITMHostnames = true
} label: {
Label(copiedMITMHostnames ? "已复制解密域名" : "复制解密域名", systemImage: "doc.on.doc")
(copiedMITMHostnames ? Label("已复制解密域名", systemImage: "doc.on.doc") : Label("复制解密域名", systemImage: "doc.on.doc"))
}
Button {
@@ -468,31 +468,31 @@ struct SettingsView: View {
private var thirdPartyStatusText: String {
switch thirdPartyProxy.connectionState {
case .unknown: return "未检测"
case .connected(let active): return active ? "已连接,有坐标" : "已连接,无坐标"
case .failed: return "连接失败"
case .unknown: return String(localized: "未检测")
case .connected(let active): return active ? String(localized: "已连接,有坐标") : String(localized: "已连接,无坐标")
case .failed: return String(localized: "连接失败")
}
}
private var virtualLocationStatusText: String {
if runtimeMode.mode == .localWiFi {
return actions.virtualLocationEnabled ? "已开启" : "已关闭"
return actions.virtualLocationEnabled ? String(localized: "已开启") : String(localized: "已关闭")
}
if case .connected(let active) = thirdPartyProxy.connectionState {
return active ? "第三方已保存" : "未保存"
return active ? String(localized: "第三方已保存") : String(localized: "未保存")
}
return "未知"
return String(localized: "未知")
}
private var workflowDescription: String {
if runtimeMode.mode == .thirdParty {
return "App 只负责地图选点、收藏和发送 WGS-84 坐标。第三方代理客户端通过模块拦截 Apple WLOC 请求并持久化当前坐标;本模式不启动本机代理,不使用 App 的 CA,也不需要配置 127.0.0.1:8888。"
return String(localized: "App 只负责地图选点、收藏和发送 WGS-84 坐标。第三方代理客户端通过模块拦截 Apple WLOC 请求并持久化当前坐标;本模式不启动本机代理,不使用 App 的 CA,也不需要配置 127.0.0.1:8888。")
}
return """
return String(localized: """
App 在设备本地运行一个代理服务器(127.0.0.1:8888)。
通过 WiFi 手动代理配置,让系统发往 Apple 定位域名(gs-loc.apple.com、gsp-ssl.ls.apple.com、bluedot.is.autonavi.com 等)的定位请求经过这个本地代理。代理使用已安装的 CA 证书对 HTTPS 流量做中间人解密,把 Apple 返回的定位坐标改写为你设置的虚拟坐标,再加密返回给系统,从而实现虚拟定位。
"""
""")
}
private func switchRuntimeMode(to newMode: ProxyRuntimeMode) {
@@ -509,8 +509,8 @@ struct SettingsView: View {
if runtimeMode.isInitialized(.thirdParty) {
do {
_ = try await thirdPartyProxy.query()
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "第三方代理模式检测通过。请关闭 Wi-Fi 中的 127.0.0.1:8888 手动代理,避免双重拦截。"
proxyOperationAlertTitle = String(localized: "模式已切换")
proxyOperationError = String(localized: "第三方代理模式检测通过。请关闭 Wi-Fi 中的 127.0.0.1:8888 手动代理,避免双重拦截。")
} catch {
openThirdPartySetup(for: error)
}
@@ -532,8 +532,8 @@ struct SettingsView: View {
let result = await setup.runVerificationTest()
setup.applyVerificationResult(result)
if result.isSuccess {
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "APP 模式环境检测通过。请停用第三方 WLOC 模块或代理连接,避免双重拦截。"
proxyOperationAlertTitle = String(localized: "模式已切换")
proxyOperationError = String(localized: "APP 模式环境检测通过。请停用第三方 WLOC 模块或代理连接,避免双重拦截。")
} else {
dismiss()
}
@@ -594,14 +594,14 @@ struct SettingsView: View {
try setup.certificateStore.reset()
runtimeMode.resetInitialization(.localWiFi)
guard await setup.prepareLocalServices() else {
proxyOperationAlertTitle = "证书重置失败"
proxyOperationAlertTitle = String(localized: "证书重置失败")
proxyOperationError = setup.message
return
}
setup.requestCertificateSetup()
dismiss()
} catch {
proxyOperationAlertTitle = "证书重置失败"
proxyOperationAlertTitle = String(localized: "证书重置失败")
proxyOperationError = error.localizedDescription
}
}
@@ -612,8 +612,8 @@ struct SettingsView: View {
UIApplication.shared.open(url, options: [:]) { opened in
guard !opened else { return }
Task { @MainActor in
proxyOperationAlertTitle = "无法打开客户端"
proxyOperationError = "无法打开 \(client.name),请确认客户端已安装后手动打开。"
proxyOperationAlertTitle = String(localized: "无法打开客户端")
proxyOperationError = String(localized: "无法打开 \(client.name),请确认客户端已安装后手动打开。")
}
}
}
+4 -4
View File
@@ -36,7 +36,7 @@ final class SetupCoordinator: ObservableObject {
message = ""
return true
} catch {
message = "本地代理初始化失败:\(error.localizedDescription)"
message = String(localized: "本地代理初始化失败:\(error.localizedDescription)")
RuntimeLogger.error("APP", "Startup", "本地服务初始化失败", error: error)
return false
}
@@ -48,19 +48,19 @@ final class SetupCoordinator: ObservableObject {
case .success:
trustState = .trusted
needsSetup = false
message = "✓ 定位环境正常"
message = String(localized: "✓ 定位环境正常")
case .certNotTrusted:
trustState = .unavailable
setupStep = .cert
needsSetup = true
message = "CA 证书未安装或未信任"
message = String(localized: "CA 证书未安装或未信任")
case .verificationInProgress, .verificationSuperseded:
break
default:
trustState = .unavailable
setupStep = .proxy
needsSetup = true
message = "Wi-Fi 代理未正确设置,请检查 127.0.0.1:8888"
message = String(localized: "Wi-Fi 代理未正确设置,请检查 127.0.0.1:8888")
}
}
+4 -4
View File
@@ -25,13 +25,13 @@ enum SystemSettingsDestination {
var manualPath: String {
switch self {
case .appPermissions:
return "请手动打开「设置」,找到本 App 后检查定位权限。"
return String(localized: "请手动打开「设置」,找到本 App 后检查定位权限。")
case .general:
return "请手动打开「设置 → 通用」。"
return String(localized: "请手动打开「设置 → 通用」。")
case .wifi:
return "请手动打开「设置 → 无线局域网」,进入当前 Wi-Fi 的详情页。"
return String(localized: "请手动打开「设置 → 无线局域网」,进入当前 Wi-Fi 的详情页。")
case .locationServices:
return "请手动打开「设置 → 隐私与安全性 → 定位服务」。"
return String(localized: "请手动打开「设置 → 隐私与安全性 → 定位服务」。")
}
}
}
+5 -5
View File
@@ -23,7 +23,7 @@ struct TipSheetView: View {
}
}.padding(16)
}
.navigationTitle(kind.rawValue).navigationBarTitleDisplayMode(.inline)
.navigationTitle(LocalizedStringKey(kind.rawValue)).navigationBarTitleDisplayMode(.inline)
.safeAreaInset(edge: .bottom) {
Button { dismiss() } label: {
Text("知道了").font(.body.weight(.medium)).frame(maxWidth: .infinity).padding(.vertical, 12)
@@ -80,7 +80,7 @@ struct ActivationTipContent: View {
}
private func step(_ n: Int, _ title: String, _ detail: String) -> some View {
private func step(_ n: Int, _ title: LocalizedStringKey, _ detail: LocalizedStringKey) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(n)").font(.caption2.bold())
.frame(width: 20, height: 20)
@@ -92,7 +92,7 @@ struct ActivationTipContent: View {
}
}
private func systemStep(_ n: Int, _ title: String, _ detail: String) -> some View {
private func systemStep(_ n: Int, _ title: LocalizedStringKey, _ detail: LocalizedStringKey) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(n)").font(.caption2.bold())
.frame(width: 20, height: 20)
@@ -141,7 +141,7 @@ struct DeactivationTipContent: View {
}
private func step(_ n: Int, _ title: String, _ detail: String) -> some View {
private func step(_ n: Int, _ title: LocalizedStringKey, _ detail: LocalizedStringKey) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(n)").font(.caption2.bold())
.frame(width: 20, height: 20)
@@ -153,7 +153,7 @@ struct DeactivationTipContent: View {
}
}
private func systemStep(_ n: Int, _ title: String, _ detail: String) -> some View {
private func systemStep(_ n: Int, _ title: LocalizedStringKey, _ detail: LocalizedStringKey) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(n)").font(.caption2.bold())
.frame(width: 20, height: 20)