mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-26 08:31:53 +08:00
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:
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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: "没有找到相关地点") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),请确认客户端已安装后手动打开。")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user