fix(i18n): 完善国际化并迁移 WLOC 脚本

This commit is contained in:
xweiba
2026-09-11 02:39:00 +08:00
parent 908ac8f734
commit 05949703af
42 changed files with 2006 additions and 160 deletions
@@ -73,9 +73,13 @@ final class RealtimeLocationManagerTests: XCTestCase {
func testOneShotTimeoutTransitionsToContinuousFallback() async {
let driver = FakeRealtimeLocationDriver()
let manager = RealtimeLocationManager(driver: driver, oneShotTimeoutNanoseconds: 5_000_000, fallbackTimeoutNanoseconds: 1_000_000_000)
let fallbackStarted = expectation(description: "Continuous location fallback started")
driver.onStartUpdating = {
fallbackStarted.fulfill()
}
let request = Task { await manager.requestLocation() }
try? await Task.sleep(nanoseconds: 20_000_000)
await fulfillment(of: [fallbackStarted], timeout: 1)
XCTAssertEqual(driver.startUpdatingCallCount, 1)
driver.emit(CLLocation(latitude: 39.90, longitude: 116.40))
@@ -148,6 +152,7 @@ private final class FakeRealtimeLocationDriver: RealtimeLocationDriving {
var authorizationStatus: CLAuthorizationStatus = .authorizedWhenInUse
weak var delegate: CLLocationManagerDelegate?
var onRequestLocation: (() -> Void)?
var onStartUpdating: (() -> Void)?
private(set) var requestAuthorizationCallCount = 0
private(set) var requestLocationCallCount = 0
private(set) var startUpdatingCallCount = 0
@@ -164,6 +169,7 @@ private final class FakeRealtimeLocationDriver: RealtimeLocationDriving {
func startUpdatingLocation() {
startUpdatingCallCount += 1
onStartUpdating?()
}
func stopUpdatingLocation() {
@@ -125,16 +125,19 @@ final class ThirdPartyProxyManagerTests: XCTestCase {
}
}
func testClientLinksUseUpstreamModulesAndVerificationLabels() {
func testClientLinksUseProjectOwnedModulesAndVerificationLabels() {
XCTAssertEqual(
ThirdPartyProxyManager.interceptionHostnamesText,
"gs-loc.apple.com, gs-loc-cn.apple.com, gsp-ssl.ls.apple.com, bluedot.is.autonavi.com, bluedot.is.autonavi.com.gds.alibabadns.com"
)
XCTAssertNil(ThirdPartyProxyClient.shadowrocket.verificationText)
XCTAssertTrue(ThirdPartyProxyClient.surge.verificationText?.contains("尚未验证") == true)
XCTAssertEqual(
ThirdPartyProxyClient.surge.verificationText,
String(localized: "配置已提供,尚未验证")
)
XCTAssertEqual(ThirdPartyProxyClient.egern.subscriptionURL, ThirdPartyProxyClient.surge.subscriptionURL)
XCTAssertTrue(ThirdPartyProxyClient.stash.subscriptionURL.absoluteString.hasPrefix(
"https://gh-proxy.org/https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/modules/"
"https://gh-proxy.org/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/"
))
let stashComponents = URLComponents(
url: ThirdPartyProxyClient.stash.subscriptionURL,
@@ -144,10 +147,10 @@ final class ThirdPartyProxyManagerTests: XCTestCase {
url: ThirdPartyProxyClient.shadowrocket.subscriptionURL,
resolvingAgainstBaseURL: false
)
XCTAssertEqual(stashComponents?.path, "/https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/modules/wloc.stoverride")
XCTAssertEqual(shadowrocketComponents?.path, "/https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/modules/wloc.module")
XCTAssertTrue(stashComponents?.queryItems?.isEmpty ?? true)
XCTAssertTrue(shadowrocketComponents?.queryItems?.isEmpty ?? true)
XCTAssertEqual(stashComponents?.path, "/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/wloc.stoverride")
XCTAssertEqual(shadowrocketComponents?.path, "/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/wloc.module")
XCTAssertEqual(stashComponents?.queryItems?.first?.value, ThirdPartyProxyClient.moduleSubscriptionVersion)
XCTAssertEqual(shadowrocketComponents?.queryItems?.first?.value, ThirdPartyProxyClient.moduleSubscriptionVersion)
XCTAssertEqual(ThirdPartyProxyClient.shadowrocket.launchURL?.scheme, "shadowrocket")
XCTAssertEqual(ThirdPartyProxyClient.surge.launchURL?.scheme, "surge")
XCTAssertEqual(ThirdPartyProxyClient.quantumultX.launchURL?.scheme, "quantumult-x")
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
STRINGS="$ROOT/Resources/en.lproj/Localizable.strings"
fail() {
echo "FAIL: $1" >&2
exit 1
}
plutil -lint "$STRINGS" >/dev/null || fail "English localization file is invalid"
ruby - "$STRINGS" <<'RUBY' || exit 1
path = ARGV.fetch(0)
entries = File.readlines(path).map do |line|
match = line.match(/^"((?:\\.|[^"])*)"\s*=\s*"((?:\\.|[^"])*)";/)
[match[1], match[2]] if match
end.compact
duplicates = entries.group_by(&:first).select { |_key, values| values.length > 1 }.keys
abort "FAIL: duplicate English localization keys: #{duplicates.join(', ')}" unless duplicates.empty?
placeholder = /%(?:\d+\$)?(?:@|lld|ld|d|f)/
entries.each do |source, target|
source_count = source.scan(placeholder).length
target_count = target.scan(placeholder).length
abort "FAIL: placeholder mismatch for #{source.inspect}" unless source_count == target_count
end
RUBY
for key in \
'环境信息' \
'诊断日志' \
'======== 代理验证测试 ========' \
'======== 第三方代理连接检测 ========' \
'======== 第三方代理运行检测 ========' \
'第三方代理测试模式:模块连接成功;已保存坐标=%@' \
'成功:{\"success\":true,\"longitude\":113.0,\"latitude\":22.0,\"accuracy\":25}\n失败:{\"success\":false,\"error\":\"错误说明\"}'; do
grep -Fq "\"$key\" = " "$STRINGS" || fail "missing critical English localization: $key"
done
grep -q 'localizedCategory' "$ROOT/Shared/RuntimeLog.swift" \
|| fail "runtime log categories must be localized when rendered"
grep -q 'localizedDetailsText' "$ROOT/App/DiagnosticsView.swift" \
|| fail "runtime log details must be localized in the diagnostics UI"
grep -q 'String(localized: "诊断日志")' "$ROOT/App/BugReportView.swift" \
|| fail "generated bug reports must localize their diagnostic section"
grep -q 'log(" " + e.localizedMessage)' "$ROOT/App/SetupCoordinator.swift" \
|| fail "bug-report verification logs must render stored messages in the active language"
if grep -R -n 'raw.githubusercontent.com/Yu9191/wloc' \
"$ROOT/App" "$ROOT/Shared" "$ROOT/Resources/ThirdPartyProxyModules" \
"$ROOT/ThirdParty/WlocScripts/modules"; then
fail "deleted Yu9191/wloc repository must not remain a runtime dependency"
fi
echo "PASS: localization and diagnostic export contract"
+20 -5
View File
@@ -10,8 +10,8 @@ CONTENT="$ROOT/App/ContentView.swift"
SETUP="$ROOT/App/FirstSetupView.swift"
SETTINGS="$ROOT/App/SettingsView.swift"
grep -q 'return "APP模式"' "$MODE" || fail "APP mode display name is missing"
grep -q 'return "第三方代理模式"' "$MODE" || fail "third-party mode display name is missing"
grep -q 'String(localized: "APP模式")' "$MODE" || fail "localized APP mode display name is missing"
grep -q 'String(localized: "第三方代理模式")' "$MODE" || fail "localized third-party mode display name is missing"
grep -q 'hasSelectedMode' "$MODE" || fail "first-launch mode selection must be persisted"
grep -q 'guard runtimeMode.hasSelectedMode else' "$CONTENT" || fail "mode selection must gate startup"
grep -q 'phase = .setup' "$CONTENT" || fail "first launch must enter setup before map construction"
@@ -37,8 +37,23 @@ grep -Fq '保存:GET ?lon=<经度>&lat=<纬度>&acc=<精度>' "$SETUP" \
grep -Fq '清除:GET ?action=clear' "$SETUP" \
|| fail "client integration guidance must document the clear action"
grep -q 'Yu9191/wloc/refs/heads/main/modules' "$MANAGER" \
|| fail "third-party subscription must point at upstream Yu9191 modules"
grep -q 'raw.githubusercontent.com/xweiba/location-spoofer/main' "$MANAGER" \
|| fail "third-party subscription must point at project-owned modules"
! grep -q 'raw.githubusercontent.com/Yu9191/wloc' "$MANAGER" \
|| fail "third-party subscription must not depend on the removed upstream repository"
MODULES="$ROOT/Resources/ThirdPartyProxyModules"
SCRIPTS="$ROOT/ThirdParty/WlocScripts"
for file in wloc.module wloc.sgmodule wloc.conf wloc.lpx wloc.stoverride; do
test -s "$MODULES/$file" || fail "missing project-owned mirrored module: $file"
test -s "$SCRIPTS/modules/direct/$file" || fail "missing project-owned direct module: $file"
! grep 'ThirdParty/WlocScripts/dist/v1/wloc' "$MODULES/$file" "$SCRIPTS/modules/direct/$file" \
| grep -Fvq '?v=1.0.7' \
|| fail "hosted script URLs must carry the current cache version: $file"
done
test -s "$SCRIPTS/dist/v1/wloc.js" || fail "missing hosted WLOC response script"
test -s "$SCRIPTS/dist/v1/wloc-settings.js" || fail "missing hosted WLOC settings script"
! grep -R -q 'raw.githubusercontent.com/Yu9191/wloc' "$MODULES" "$SCRIPTS/modules/direct" \
|| fail "hosted modules must not reference the removed upstream repository"
grep -q 'wloc.sgmodule' "$MANAGER" || fail "Surge/Egern module mapping is missing"
grep -q 'wloc.stoverride' "$MANAGER" || fail "Stash must use .stoverride directly"
grep -q 'shadowrocket://' "$MANAGER" || fail "Shadowrocket launch URL is missing"
@@ -88,7 +103,7 @@ grep -q 'setup.requestThirdPartySetup(message: error.localizedDescription)' "$RO
|| fail "third-party coordinate sync failures must open the import guide"
grep -q '检测到第三方代理连接异常,请检查模块、MITM 和代理连接后重新检测' "$SETUP" \
|| fail "runtime repair must explain why the import guide opened"
test "$(grep -c 'title: \"接口连接失败\"' "$SETUP")" -eq 1 \
test "$(grep -c 'title: String(localized: \"接口连接失败\")' "$SETUP")" -eq 1 \
|| fail "third-party failure details must render in one shared result area"
grep -Fq '当前客户端:\(client.name)' "$SETUP" \
|| fail "third-party failure logs must identify the selected client"
@@ -25,7 +25,7 @@ import sys
with open(sys.argv[1], encoding="utf-8") as handle:
config = json.load(handle)
assert config["latestVersion"] == "1.0.5"
assert config["latestVersion"] == "1.0.6"
assert config["minimumSupportedVersion"] == "1.0.0"
assert "shadowrocket" not in config["communityPromptClients"]
assert set(config["communityPromptClients"]) == {
@@ -121,7 +121,7 @@ grep -Fq 'SafariView(url: destination.url)' "$BUG_REPORT" \
|| fail "the App bug report must not be handed to an external GitHub client"
grep -Fq 'App 生成的诊断报告' "$BUG_REPORT" \
|| fail "the App must tell users where to paste the generated report"
grep -Fq '第三方客户端:' "$BUG_REPORT" \
grep -Fq 'String(localized: "第三方客户端")' "$BUG_REPORT" \
|| fail "the generated report must identify the selected third-party client"
grep -Fq 'Label("报告 Bug"' "$SETTINGS" \
|| fail "Settings must identify the support action as a bug report"