From 2ddd914b4cd7d08899efcc0d7bea916f941f287d Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 19:36:29 +0800 Subject: [PATCH 01/14] Added Chinese Localization #1 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 StikJIT/Assets.xcassets/Localizable.xcstrings diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings new file mode 100644 index 00000000..900453da --- /dev/null +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -0,0 +1,7 @@ +{ + "sourceLanguage" : "en", + "strings" : { + + }, + "version" : "1.1" +} \ No newline at end of file From 9a35bf3d185a950c9c34cfe966ea4fc8fa7c54ee Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 19:37:03 +0800 Subject: [PATCH 02/14] #2 --- .gitignore | 5 + StikDebug.xcodeproj/project.pbxproj | 13 +- .../xcschemes/DebugWidgetExtension.xcscheme | 3 +- .../xcshareddata/xcschemes/StikDebug.xcscheme | 2 +- StikJIT/Assets.xcassets/Localizable.xcstrings | 1378 ++++++++++++++++- 5 files changed, 1396 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 17d36288..cad6d0e8 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,8 @@ iOSInjectionProject/ .DS_Store .vscode/settings.json .vscode + + +package.json +package-lock.json +node_modules/ \ No newline at end of file diff --git a/StikDebug.xcodeproj/project.pbxproj b/StikDebug.xcodeproj/project.pbxproj index 33127f18..6c880407 100644 --- a/StikDebug.xcodeproj/project.pbxproj +++ b/StikDebug.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 43953ACC2F9246C6006CB77E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */; }; 68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; }; 68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; }; /* End PBXBuildFile section */ @@ -42,6 +43,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = StikJIT/Assets.xcassets/Localizable.xcstrings; sourceTree = ""; }; DC139F6D2DE97EA400F63846 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; DC139F6F2DE97EA400F63846 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; DC6F1D372D94EADD0071B2B6 /* StikDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StikDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -110,6 +112,7 @@ DC6F1D2E2D94EADD0071B2B6 = { isa = PBXGroup; children = ( + 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */, DC6F1D392D94EADD0071B2B6 /* StikJIT */, DC6F1D4B2D94EADF0071B2B6 /* StikJITTests */, DC6F1D552D94EADF0071B2B6 /* StikJITUITests */, @@ -219,7 +222,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1620; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 2620; TargetAttributes = { DC6F1D362D94EADD0071B2B6 = { CreatedOnToolsVersion = 16.2; @@ -243,6 +246,7 @@ Base, es, it, + "zh-Hans", ); mainGroup = DC6F1D2E2D94EADD0071B2B6; minimizedProjectReferenceProxies = 1; @@ -266,6 +270,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 43953ACC2F9246C6006CB77E /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -328,6 +333,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -379,7 +385,9 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -389,6 +397,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -433,7 +442,9 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; }; name = Release; }; diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme index c2efed42..1ce5d7a8 100644 --- a/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme +++ b/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme @@ -1,6 +1,6 @@ diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme index cf047ee7..988f1fef 100644 --- a/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme +++ b/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 17 Apr 2026 20:44:01 +0800 Subject: [PATCH 03/14] zh-Hans Localizations #3 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 104 +++++++++++++++++- StikJIT/Views/MainTabView.swift | 16 +-- StikJIT/Views/ToolsView.swift | 14 +-- 3 files changed, 118 insertions(+), 16 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 3fbc0ade..80de43ac 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -1377,7 +1377,109 @@ } } } - } + }, + "Search apps or bundle ID": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search apps or bundle ID" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索应用或应用ID" + } + } + } + }, + "Reset Script": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reset Script" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置脚本" + } + } + } + }, + "Tools": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tools" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工具" + } + } + } + }, + "Target Device IP": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Target Device IP" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标设备IP" + } + } + } + }, + "Star on Github": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Star on Github" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "给颗星星吧awa~" + } + } + } + }, + "Silent Audio": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Silent Audio" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无声音频" + } + } + } + }, }, "version" : "1.1" } \ No newline at end of file diff --git a/StikJIT/Views/MainTabView.swift b/StikJIT/Views/MainTabView.swift index 84d017c1..42e44708 100644 --- a/StikJIT/Views/MainTabView.swift +++ b/StikJIT/Views/MainTabView.swift @@ -27,13 +27,13 @@ struct MainTabView: View { private var configurableTabs: [TabDescriptor] { let tabs: [TabDescriptor] = [ - TabDescriptor(id: "home", title: "Apps", systemImage: "square.grid.2x2") { AnyView(HomeView()) }, - TabDescriptor(id: "scripts", title: "Scripts", systemImage: "scroll") { AnyView(ScriptListView()) }, - TabDescriptor(id: "tools", title: "Tools", systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, - TabDescriptor(id: "deviceinfo", title: "Device Info", systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, - TabDescriptor(id: "profiles", title: "App Expiry", systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, - TabDescriptor(id: "processes", title: "Processes", systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, - TabDescriptor(id: "location", title: "Location", systemImage: "location") { AnyView(LocationSimulationView()) } + TabDescriptor(id: "home", title: NSLocalizedString("Apps", comment: ""), systemImage: "square.grid.2x2") { AnyView(HomeView()) }, + TabDescriptor(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), systemImage: "scroll") { AnyView(ScriptListView()) }, + TabDescriptor(id: "tools", title: NSLocalizedString("Tools", comment: ""), systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, + TabDescriptor(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, + TabDescriptor(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, + TabDescriptor(id: "processes", title: NSLocalizedString("Processes", comment: ""), systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, + TabDescriptor(id: "location", title: NSLocalizedString("Location", comment: ""), systemImage: "location") { AnyView(LocationSimulationView()) } ] return tabs } @@ -42,7 +42,7 @@ struct MainTabView: View { configurableTabs } - private let settingsTab = TabDescriptor(id: "settings", title: "Settings", systemImage: "gearshape.fill") { + private let settingsTab = TabDescriptor(id: "settings", title: NSLocalizedString("Settings", comment: ""), systemImage: "gearshape.fill") { AnyView(SettingsView()) } diff --git a/StikJIT/Views/ToolsView.swift b/StikJIT/Views/ToolsView.swift index bf274bb9..8b2f548b 100644 --- a/StikJIT/Views/ToolsView.swift +++ b/StikJIT/Views/ToolsView.swift @@ -18,12 +18,12 @@ struct ToolsView: View { private var tools: [ToolItem] { [ - ToolItem(id: "scripts", title: "Scripts", detail: "Manage and run JS scripts", systemImage: "scroll", destination: AnyView(ScriptListView())), - ToolItem(id: "console", title: "Console", detail: "Live device logs", systemImage: "terminal", destination: AnyView(ConsoleLogsView())), - ToolItem(id: "deviceinfo", title: "Device Info", detail: "View detailed device metadata", systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), - ToolItem(id: "profiles", title: "App Expiry", detail: "Check app expiration dates", systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), - ToolItem(id: "processes", title: "Processes", detail: "Inspect running apps", systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), - ToolItem(id: "location", title: "Location Simulation", detail: "Simulate GPS location", systemImage: "location", destination: AnyView(LocationSimulationView())) + ToolItem(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage and run JS scripts", comment: ""), systemImage: "scroll", destination: AnyView(ScriptListView())), + ToolItem(id: "console", title: NSLocalizedString("Console", comment: ""), detail: NSLocalizedString("Live device logs", comment: ""), systemImage: "terminal", destination: AnyView(ConsoleLogsView())), + ToolItem(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), + ToolItem(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration dates", comment: ""), systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), + ToolItem(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), + ToolItem(id: "location", title: NSLocalizedString("Location Simulation", comment: ""), detail: NSLocalizedString("Simulate GPS location", comment: ""), systemImage: "location", destination: AnyView(LocationSimulationView())) ] } @@ -45,7 +45,7 @@ struct ToolsView: View { } } } - .navigationTitle("Tools") + .navigationTitle(NSLocalizedString("Tools", comment: "")) } } } From 78fa4ce2230127733145c8567aa6130d39c52b04 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 21:19:07 +0800 Subject: [PATCH 04/14] zh-Hans Localization #4 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 442 ++++++++++++++++++ StikJIT/Views/SettingsView.swift | 82 ++-- 2 files changed, 483 insertions(+), 41 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 80de43ac..7f9d50d9 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -1480,6 +1480,448 @@ } } }, + "Access additional tools": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Access additional tools" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "访问其他工具" + } + } + } + }, + "Check app expiration date, install/remove profiles": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check app expiration date, install/remove profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查应用过期日期,并安装/删除配置" + } + } + } + }, + "Check app expiration dates": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check app expiration dates" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查应用过期日期" + } + } + } + }, + "Dashboard overview": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dashboard overview" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仪表板概览" + } + } + } + }, + "DDI files refreshed successfully.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DDI files refreshed successfully." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开发者镜像刷新成功。" + } + } + } + }, + "Existing DDI files will be removed before downloading fresh copies.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Existing DDI files will be removed before downloading fresh copies." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "现有开发者镜像将在下载新副本之前被删除。" + } + } + } + }, + "Failed to redownload DDI files: %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to redownload DDI files: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载开发者镜像失败:%@" + } + } + } + }, + "Imported successfully": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imported successfully" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入成功" + } + } + } + }, + "Inspect running apps": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inspect running apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查运行中的应用" + } + } + } + }, + "Location Sim": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Location Sim" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "虚拟定位" + } + } + } + }, + "Manage automation scripts": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage automation scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理自动化脚本" + } + } + } + }, + "Non TXM": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non TXM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "非TXM" + } + } + } + }, + "Plays inaudible audio so iOS keeps the app running.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plays inaudible audio so iOS keeps the app running." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "播放无声音频以保持应用运行。" + } + } + } + }, + "Preparing download…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preparing download…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "准备下载…" + } + } + } + }, + "Processing pairing file…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Processing pairing file…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "处理配对文件…" + } + } + } + }, + "Redownload DDI Files?": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redownload DDI Files?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载开发者镜像?" + } + } + } + }, + "Settings is fixed as the 4th tab.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings is fixed as the 4th tab." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置已经固定为第4个标签。" + } + } + } + }, + "Sideload only": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sideload only" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅侧载" + } + } + } + }, + "Simulate GPS location": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate GPS location" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟GPS位置" + } + } + } + }, + "Tab Bar": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tab Bar" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标签栏" + } + } + } + }, + "Treats device as TXM-capable to bypass hardware checks.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Treats device as TXM-capable to bypass hardware checks." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将设备视为支持TXM以绕过硬件检查。" + } + } + } + }, + "TXM": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM" + } + } + } + }, + "TXM (Override)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM (Override)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM(覆盖)" + } + } + } + }, + "Uses low-accuracy location to stay alive when an activity needs it.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uses low-accuracy location to stay alive when an activity needs it." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在需要时使用低精度定位保活。" + } + } + } + }, + "Version %@ • iOS %@ • %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Version %@ • iOS %@ • %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本 %@ • iOS %@ • %@" + } + } + } + }, + "View detailed device metadata": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "View detailed device metadata" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看详细设备元数据" + } + } + } + } }, "version" : "1.1" } \ No newline at end of file diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 83f0cb37..261c772b 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -46,14 +46,14 @@ struct SettingsView: View { private var tabOptions: [TabOption] { var options: [TabOption] = [ - TabOption(id: "home", title: "Home", detail: "Dashboard overview", icon: "house", isBeta: false), - TabOption(id: "scripts", title: "Scripts", detail: "Manage automation scripts", icon: "scroll", isBeta: false), - TabOption(id: "tools", title: "Tools", detail: "Access additional tools", icon: "wrench.and.screwdriver", isBeta: false) + TabOption(id: "home", title: NSLocalizedString("Home", comment: ""), detail: NSLocalizedString("Dashboard overview", comment: ""), icon: "house", isBeta: false), + TabOption(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage automation scripts", comment: ""), icon: "scroll", isBeta: false), + TabOption(id: "tools", title: NSLocalizedString("Tools", comment: ""), detail: NSLocalizedString("Access additional tools", comment: ""), icon: "wrench.and.screwdriver", isBeta: false) ] - options.append(TabOption(id: "deviceinfo", title: "Device Info", detail: "View detailed device metadata", icon: "iphone.and.arrow.forward", isBeta: false)) - options.append(TabOption(id: "profiles", title: "App Expiry", detail: "Check app expiration date, install/remove profiles", icon: "calendar.badge.clock", isBeta: false)) - options.append(TabOption(id: "processes", title: "Processes", detail: "Inspect running apps", icon: "rectangle.stack.person.crop", isBeta: false)) - options.append(TabOption(id: "location", title: "Location Sim", detail: "Sideload only", icon: "location", isBeta: false)) + options.append(TabOption(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), icon: "iphone.and.arrow.forward", isBeta: false)) + options.append(TabOption(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration date, install/remove profiles", comment: ""), icon: "calendar.badge.clock", isBeta: false)) + options.append(TabOption(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), icon: "rectangle.stack.person.crop", isBeta: false)) + options.append(TabOption(id: "location", title: NSLocalizedString("Location Sim", comment: ""), detail: NSLocalizedString("Sideload only", comment: ""), icon: "location", isBeta: false)) return options } @@ -80,17 +80,17 @@ struct SettingsView: View { // 2) GitHub Section { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug/stargazers")!) { - Label("Star on GitHub", systemImage: "star") + Label(NSLocalizedString("Star on GitHub", comment: ""), systemImage: "star") } } // 3) Pairing File - Section("Pairing File") { + Section(NSLocalizedString("Pairing File", comment: "")) { Button { isShowingPairingFilePicker = true } label: { - Label("Import Pairing File", systemImage: "doc.badge.plus") + Label(NSLocalizedString("Import Pairing File", comment: ""), systemImage: "doc.badge.plus") } if showPairingFileMessage && !isImportingFile { - Label("Imported successfully", systemImage: "checkmark.circle.fill") + Label(NSLocalizedString("Imported successfully", comment: ""), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } } @@ -99,8 +99,8 @@ struct SettingsView: View { Section { Toggle(isOn: $keepAliveAudio) { VStack(alignment: .leading, spacing: 2) { - Text("Silent Audio") - Text("Plays inaudible audio so iOS keeps the app running.") + Text(NSLocalizedString("Silent Audio", comment: "")) + Text(NSLocalizedString("Plays inaudible audio so iOS keeps the app running.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } @@ -111,8 +111,8 @@ struct SettingsView: View { Toggle(isOn: $keepAliveLocation) { VStack(alignment: .leading, spacing: 2) { - Text("Background Location") - Text("Uses low-accuracy location to stay alive when an activity needs it.") + Text(NSLocalizedString("Background Location", comment: "")) + Text(NSLocalizedString("Uses low-accuracy location to stay alive when an activity needs it.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } @@ -121,24 +121,24 @@ struct SettingsView: View { } } header: { - Text("Background Keep-Alive") + Text(NSLocalizedString("Background Keep-Alive", comment: "")) } // 6) Behavior - Section("Behavior") { + Section(NSLocalizedString("Behavior", comment: "")) { Toggle(isOn: $overrideTXMDetection) { VStack(alignment: .leading, spacing: 2) { - Text("Always Run Scripts") - Text("Treats device as TXM-capable to bypass hardware checks.") + Text(NSLocalizedString("Always Run Scripts", comment: "")) + Text(NSLocalizedString("Treats device as TXM-capable to bypass hardware checks.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } } // 7) Advanced - Section("Advanced") { + Section(NSLocalizedString("Advanced", comment: "")) { HStack { - Text("Target Device IP") + Text(NSLocalizedString("Target Device IP", comment: "")) Spacer() TextField("10.7.0.1", text: $customTargetIP) .multilineTextAlignment(.trailing) @@ -146,10 +146,10 @@ struct SettingsView: View { .submitLabel(.done) } Button { openAppFolder() } label: { - Label("App Folder", systemImage: "folder") + Label(NSLocalizedString("App Folder", comment: ""), systemImage: "folder") }.foregroundStyle(.primary) Button { showDDIConfirmation = true } label: { - Label("Redownload DDI", systemImage: "arrow.down.circle") + Label(NSLocalizedString("Redownload DDI", comment: ""), systemImage: "arrow.down.circle") }.foregroundStyle(.primary).disabled(isRedownloadingDDI) if isRedownloadingDDI { VStack(alignment: .leading, spacing: 4) { @@ -162,15 +162,15 @@ struct SettingsView: View { } // 7) Help - Section("Help") { + Section(NSLocalizedString("Help", comment: "")) { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug-Guide/blob/main/pairing_file.md")!) { - Label("Pairing File Guide", systemImage: "questionmark.circle") + Label(NSLocalizedString("Pairing File Guide", comment: ""), systemImage: "questionmark.circle") } Link(destination: URL(string: "https://apps.apple.com/us/app/localdevvpn/id6755608044")!) { - Label("Download LocalDevVPN", systemImage: "arrow.down.circle") + Label(NSLocalizedString("Download LocalDevVPN", comment: ""), systemImage: "arrow.down.circle") } Link(destination: URL(string: "https://discord.gg/qahjXNTDwS")!) { - Label("Discord Support", systemImage: "bubble.left.and.bubble.right") + Label(NSLocalizedString("Discord Support", comment: ""), systemImage: "bubble.left.and.bubble.right") } } @@ -182,7 +182,7 @@ struct SettingsView: View { .listRowBackground(Color.clear) } } - .navigationTitle("Settings") + .navigationTitle(NSLocalizedString("Settings", comment: "")) } .fileImporter( isPresented: $isShowingPairingFilePicker, @@ -225,13 +225,13 @@ struct SettingsView: View { break } } - .confirmationDialog("Redownload DDI Files?", isPresented: $showDDIConfirmation, titleVisibility: .visible) { - Button("Redownload", role: .destructive) { + .confirmationDialog(NSLocalizedString("Redownload DDI Files?", comment: ""), isPresented: $showDDIConfirmation, titleVisibility: .visible) { + Button(NSLocalizedString("Redownload", comment: ""), role: .destructive) { redownloadDDIPressed() } - Button("Cancel", role: .cancel) { } + Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) { } } message: { - Text("Existing DDI files will be removed before downloading fresh copies.") + Text(NSLocalizedString("Existing DDI files will be removed before downloading fresh copies.", comment: "")) } .overlay { if isImportingFile { importBusyOverlay } } } @@ -240,7 +240,7 @@ struct SettingsView: View { private var importBusyOverlay: some View { Color.black.opacity(0.35).ignoresSafeArea() VStack(spacing: 12) { - ProgressView("Processing pairing file…") + ProgressView(NSLocalizedString("Processing pairing file…", comment: "")) VStack(spacing: 8) { GeometryReader { geometry in ZStack(alignment: .leading) { @@ -276,11 +276,11 @@ struct SettingsView: View { let processInfo = ProcessInfo.processInfo let txmLabel: String if processInfo.isTXMOverridden { - txmLabel = "TXM (Override)" + txmLabel = NSLocalizedString("TXM (Override)", comment: "") } else { - txmLabel = processInfo.hasTXM ? "TXM" : "Non TXM" + txmLabel = processInfo.hasTXM ? NSLocalizedString("TXM", comment: "") : NSLocalizedString("Non TXM", comment: "") } - return "Version \(appVersion) • iOS \(UIDevice.current.systemVersion) • \(txmLabel)" + return String(format: NSLocalizedString("Version %@ • iOS %@ • %@", comment: ""), appVersion, UIDevice.current.systemVersion, txmLabel) } // MARK: - Business Logic @@ -299,7 +299,7 @@ struct SettingsView: View { await MainActor.run { isRedownloadingDDI = true ddiDownloadProgress = 0 - ddiStatusMessage = "Preparing download…" + ddiStatusMessage = NSLocalizedString("Preparing download…", comment: "") ddiResultMessage = nil } do { @@ -368,13 +368,13 @@ struct TabCustomizationView: View { enabledTabIdentifiers = TabConfiguration.serialize(ids) } } header: { - Text("Pinned") + Text(NSLocalizedString("Pinned", comment: "")) } footer: { - Text("Settings is fixed as the 4th tab.") + Text(NSLocalizedString("Settings is fixed as the 4th tab.", comment: "")) } if !availableOptions.isEmpty { - Section("Available") { + Section(NSLocalizedString("Available", comment: "")) { ForEach(availableOptions) { option in Button { var ids = selectedIDs @@ -391,7 +391,7 @@ struct TabCustomizationView: View { } } } - .navigationTitle("Tab Bar") + .navigationTitle(NSLocalizedString("Tab Bar", comment: "")) .toolbar { EditButton() } From 91fe7c81ba56a9b4ca9ab11a49dd3935cd661287 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 21:40:34 +0800 Subject: [PATCH 05/14] zh-Hans Localization #5 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 155 +++++++++++++++++- StikJIT/Views/ProfileView.swift | 2 +- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 7f9d50d9..083fb814 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -1446,7 +1446,7 @@ } } }, - "Star on Github": { + "Star on GitHub": { "extractionState" : "manual", "localizations" : { "en" : { @@ -1921,6 +1921,159 @@ } } } + }, + "Search scripts…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search scripts…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索脚本…" + } + } + } + }, + "Scripts": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "脚本" + } + } + } + }, + "System": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "System" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统" + } + } + } + }, + "Settings": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" + } + } + } + }, + "Pause syslog stream":{ + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause syslog stream" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停系统日志输出" + } + } + } + }, + "Search device info…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search device info…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索设备信息…" + } + } + } + }, + "Total Processes": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total Processes" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总进程数量" + } + } + } + }, + "Search location…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search location…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索地址…" + } + } + } + }, + "Expires: %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expires: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在%@之后过期" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 2f8e948e..8f31f994 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -158,7 +158,7 @@ struct ProfileView: View { if let match = entry.bestMatchingProfile { HStack { Image(systemName: "clock") - Text("Expires: \(match.profile.formattedDate)") + Text(String(format: NSLocalizedString("Expires: %@", comment: ""), match.profile.formattedDate)) } .foregroundStyle(match.profile.dateColor) .font(.subheadline) From cb4f6d62c11eebe6755c76ac6f5a07eb206510fe Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 22:08:00 +0800 Subject: [PATCH 06/14] Localization #6 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 329 ++++++++++++++++-- StikJIT/Views/ConsoleLogsView.swift | 4 +- StikJIT/Views/MapSelectionView.swift | 12 +- 3 files changed, 317 insertions(+), 28 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 083fb814..771313a7 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -545,23 +545,6 @@ } } }, - "End" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "End" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "结束" - } - } - } - }, "Export Failed" : { "extractionState" : "manual", "localizations" : { @@ -1203,7 +1186,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "固定" + "value" : "选定地点" } } } @@ -1237,7 +1220,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "启动方式(?)" + "value" : "启用路线" } } } @@ -1480,6 +1463,125 @@ } } }, + "Start syslog relay": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start syslog relay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动系统日志输出" + } + } + } + }, + "Simulation Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulation Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "模拟失败" + } + } + } + }, + "Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "无法模拟位置(错误 %d)。请确保设备已连接且DDI已挂载。" + } + } + } + }, + "Route Simulation Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Route Simulation Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "路线模拟失败" + } + } + } + }, + "Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "无法启动路线模拟(错误 %d)。请确保设备已连接且DDI已挂载。" + } + } + } + }, + "Resume syslog stream": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume syslog stream" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复系统日志输出" + } + } + } + }, + "Pause syslog stream": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause syslog stream" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停系统日志输出" + } + } + } + }, "Access additional tools": { "extractionState" : "manual", "localizations" : { @@ -1956,6 +2058,23 @@ } } }, + "Save": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + } + } + }, "System": { "extractionState" : "manual", "localizations" : { @@ -2041,7 +2160,7 @@ } } }, - "Search location…": { + "Search location...": { "extractionState" : "manual", "localizations" : { "en" : { @@ -2074,6 +2193,176 @@ } } } + }, + "Simulate Route": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate Route" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟路线" + } + } + } + }, + "Use Route": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Use Route" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用路线" + } + } + } + }, + "Start": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "起点" + } + } + } + }, + "End": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "End" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终点" + } + } + } + }, + "Search for a start and destination to build the route.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search for a start and destination to build the route." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索起终点以建立路线" + } + } + } + }, + "Resolving location…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resolving location…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "建立路线中…" + } + } + } + }, + "Simulate Location": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate Location" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟定位" + } + } + } + }, + "Stop": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止" + } + } + } + }, + "Tap map to drop pin": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tap map to drop pin" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击地图来固定位置" + } + } + } + }, + "Drop a pin on the map and tap the bookmark icon to save a location.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Drop a pin on the map and tap the bookmark icon to save a location." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在地图上标记地点以保存位置" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index ac592e83..a21a2a40 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -534,9 +534,9 @@ struct ConsoleLogsView: View { private var syslogControlLabel: String { if !systemLogStream.isStreaming { - return "Start syslog relay" + return NSLocalizedString("Start syslog relay", comment: "") } - return systemLogStream.isPaused ? "Resume syslog stream" : "Pause syslog stream" + return systemLogStream.isPaused ? NSLocalizedString("Resume syslog stream", comment: "") : NSLocalizedString("Pause syslog stream", comment: "") } private func presentAlert(title: String, message: String) { diff --git a/StikJIT/Views/MapSelectionView.swift b/StikJIT/Views/MapSelectionView.swift index b787e583..7ef26232 100644 --- a/StikJIT/Views/MapSelectionView.swift +++ b/StikJIT/Views/MapSelectionView.swift @@ -804,9 +804,9 @@ struct LocationSimulationView: View { private func simulate() { guard pairingExists, let coord = coordinate, !isBusy else { return } runLocationCommand( - errorTitle: "Simulation Failed", + errorTitle: NSLocalizedString("Simulation Failed", comment: ""), errorMessage: { code in - "Could not simulate location (error \(code)). Make sure the device is connected and the DDI is mounted." + String(format: NSLocalizedString("Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) }, operation: { locationUpdateCode(for: coord) } ) { @@ -827,9 +827,9 @@ struct LocationSimulationView: View { stopResendLoop() cancelRoutePlayback(resetMarker: false) runLocationCommand( - errorTitle: "Route Simulation Failed", + errorTitle: NSLocalizedString("Route Simulation Failed", comment: ""), errorMessage: { code in - "Could not start route simulation (error \(code)). Make sure the device is connected and the DDI is mounted." + String(format: NSLocalizedString("Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) }, operation: { locationUpdateCode(for: firstCoordinate) } ) { @@ -1137,7 +1137,7 @@ private struct RouteSearchSheet: View { NavigationStack { VStack(alignment: .leading, spacing: 16) { routeField( - title: "Start", + title: NSLocalizedString("Start", comment: ""), icon: "circle.fill", tint: .green, text: $startQuery, @@ -1146,7 +1146,7 @@ private struct RouteSearchSheet: View { ) routeField( - title: "End", + title: NSLocalizedString("End", comment: ""), icon: "flag.checkered.circle.fill", tint: .red, text: $endQuery, From b451a4a9bfa5bf76c532eb1c9e4855324dbaf606 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 22:18:05 +0800 Subject: [PATCH 07/14] Localization #7 --- StikJIT/Assets.xcassets/Localizable.xcstrings | 68 +++++++++++++++++++ StikJIT/Utilities/DeviceInfoManager.swift | 6 +- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 771313a7..cc677b87 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -1582,6 +1582,57 @@ } } }, + "Copy All (Text)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy All (Text)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "复制全部(文本)" + } + } + } + }, + "Copy All (CSV)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy All (CSV)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "复制全部(CSV)" + } + } + } + }, + "Share…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "分享…" + } + } + } + }, "Access additional tools": { "extractionState" : "manual", "localizations" : { @@ -2363,6 +2414,23 @@ } } } + }, + "Copied": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copied" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已复制" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/Utilities/DeviceInfoManager.swift b/StikJIT/Utilities/DeviceInfoManager.swift index 51201706..c7307bee 100644 --- a/StikJIT/Utilities/DeviceInfoManager.swift +++ b/StikJIT/Utilities/DeviceInfoManager.swift @@ -253,13 +253,13 @@ struct DeviceInfoView: View { Menu { Button { copyAllText() } label: { - Label("Copy All (Text)", systemImage: "doc.on.doc") + Label(NSLocalizedString("Copy All (Text)", comment: ""), systemImage: "doc.on.doc") } Button { copyAllCSV() } label: { - Label("Copy All (CSV)", systemImage: "tablecells") + Label(NSLocalizedString("Copy All (CSV)", comment: ""), systemImage: "tablecells") } Button { shareAll() } label: { - Label("Share…", systemImage: "square.and.arrow.up.on.square") + Label(NSLocalizedString("Share…", comment: ""), systemImage: "square.and.arrow.up.on.square") } } label: { Image(systemName: "ellipsis.circle") From 5e375413920a05dc7c5e9e2d7af3d2dc547d0d81 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 22:28:55 +0800 Subject: [PATCH 08/14] Localization #8 (Ready for PRs) --- StikJIT/Assets.xcassets/Localizable.xcstrings | 51 ++++++++++++------- StikJIT/Views/ProfileView.swift | 2 +- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index cc677b87..16c635f6 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -1633,6 +1633,23 @@ } } }, + "Show %d older profiles": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Show %d older profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "显示 %d 个旧配置" + } + } + } + }, "Access additional tools": { "extractionState" : "manual", "localizations" : { @@ -2160,23 +2177,6 @@ } } }, - "Pause syslog stream":{ - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause syslog stream" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "暂停系统日志输出" - } - } - } - }, "Search device info…": { "extractionState" : "manual", "localizations" : { @@ -2431,6 +2431,23 @@ } } } + }, + "Search": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 8f31f994..008f6912 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -203,7 +203,7 @@ struct ProfileView: View { else { expandedApps.insert(entry.id) } } } label: { - Label(showMore ? "Hide older profiles" : "Show \(extraProfiles.count) older profiles", + Label(showMore ? NSLocalizedString("Hide older profiles", comment: "") : String(format: NSLocalizedString("Show %d older profiles", comment: ""), extraProfiles.count), systemImage: showMore ? "chevron.up" : "chevron.down") .font(.caption) .foregroundStyle(.blue) From 7d7caeba8cf827ad81e62cd85074a1b1b81f843f Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 22:36:44 +0800 Subject: [PATCH 09/14] added xcpretty --- .github/workflows/build_ipa.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_ipa.yml b/.github/workflows/build_ipa.yml index 3b636bf3..698ab781 100644 --- a/.github/workflows/build_ipa.yml +++ b/.github/workflows/build_ipa.yml @@ -40,7 +40,8 @@ jobs: CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO \ SWIFT_OPTIMIZATION_LEVEL="-Onone" \ - IPHONEOS_DEPLOYMENT_TARGET=17.4 + IPHONEOS_DEPLOYMENT_TARGET=17.4 \ + | xcpretty - name: 4. Package .app into .ipa run: | From 7f264423c2dac861de2f71c1edfe6af2a700de3f Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Fri, 17 Apr 2026 22:42:54 +0800 Subject: [PATCH 10/14] Enhancement of Localization --- StikJIT/Assets.xcassets/Localizable.xcstrings | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 16c635f6..2f1ebcb9 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -35,6 +35,23 @@ } } }, + "Advanced": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advanced" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级" + } + } + } + }, "Add to Home" : { "extractionState" : "manual", "localizations" : { @@ -506,7 +523,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "确认完成" + "value" : "完成" } } } From 785811ac4aeff3ad16c86de1378d952f220c8462 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Wed, 13 May 2026 18:56:22 +0800 Subject: [PATCH 11/14] zh-Hans Translation added --- StikDebug.xcodeproj/project.pbxproj | 11 + StikJIT/Assets.xcassets/Localizable.xcstrings | 2471 +++++++++++++++++ StikJIT/Utilities/DeviceInfoManager.swift | 6 +- StikJIT/Views/ConsoleLogsView.swift | 4 +- StikJIT/Views/MainTabView.swift | 16 +- StikJIT/Views/MapSelectionView.swift | 12 +- StikJIT/Views/ProfileView.swift | 4 +- StikJIT/Views/SettingsView.swift | 82 +- StikJIT/Views/ToolsView.swift | 14 +- 9 files changed, 2551 insertions(+), 69 deletions(-) create mode 100644 StikJIT/Assets.xcassets/Localizable.xcstrings diff --git a/StikDebug.xcodeproj/project.pbxproj b/StikDebug.xcodeproj/project.pbxproj index 33127f18..f5bcc4e6 100644 --- a/StikDebug.xcodeproj/project.pbxproj +++ b/StikDebug.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 43953ACC2F9246C6006CB77E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */; }; 68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; }; 68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; }; /* End PBXBuildFile section */ @@ -42,6 +43,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = StikJIT/Assets.xcassets/Localizable.xcstrings; sourceTree = ""; }; DC139F6D2DE97EA400F63846 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; DC139F6F2DE97EA400F63846 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; DC6F1D372D94EADD0071B2B6 /* StikDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StikDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -110,6 +112,7 @@ DC6F1D2E2D94EADD0071B2B6 = { isa = PBXGroup; children = ( + 43953ACB2F9246C6006CB77E /* Localizable.xcstrings */, DC6F1D392D94EADD0071B2B6 /* StikJIT */, DC6F1D4B2D94EADF0071B2B6 /* StikJITTests */, DC6F1D552D94EADF0071B2B6 /* StikJITUITests */, @@ -243,6 +246,7 @@ Base, es, it, + "zh-Hans", ); mainGroup = DC6F1D2E2D94EADD0071B2B6; minimizedProjectReferenceProxies = 1; @@ -266,6 +270,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 43953ACC2F9246C6006CB77E /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -328,6 +333,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -379,7 +385,9 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -389,6 +397,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -433,7 +442,9 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; }; name = Release; }; diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings new file mode 100644 index 00000000..8ba8bb1e --- /dev/null +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -0,0 +1,2471 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Add" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加" + } + } + } + }, + "Add to Favorites" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add to Favorites" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加到收藏" + } + } + } + }, + "Advanced": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advanced" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级" + } + } + } + }, + "Add to Home" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add to Home" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加到主屏幕" + } + } + } + }, + "Adjust Speed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adjust Speed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "调整速度" + } + } + } + }, + "All Apps" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "All Apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有应用" + } + } + } + }, + "Always Run Scripts" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Always Run Scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总是运行脚本" + } + } + } + }, + "App" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用" + } + } + } + }, + "App Expiry" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Expiry" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用过期期限" + } + } + } + }, + "App Folder" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Folder" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用文件夹" + } + } + } + }, + "Apps" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用" + } + } + } + }, + "Apps with get-task-allow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apps with get-task-allow" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "允许get-task-allow的应用" + } + } + } + }, + "Assign Script" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assign Script" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分配脚本" + } + } + } + }, + "Available" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Available" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可用" + } + } + } + }, + "Background Keep-Alive" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Background Keep-Alive" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后台保活" + } + } + } + }, + "Background Location" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Background Location" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后台定位" + } + } + } + }, + "Behavior" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Behavior" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "行为" + } + } + } + }, + "Bookmarks" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bookmarks" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "书签" + } + } + } + }, + "Busy" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Busy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "繁忙" + } + } + } + }, + "Cancel" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "取消" + } + } + } + }, + "Clear" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clear" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除" + } + } + } + }, + "Close" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Close" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + } + } + }, + "Confirm" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" + } + } + } + }, + "Confirm Removal" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm Removal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确认删除" + } + } + } + }, + "Console" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Console" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "控制台" + } + } + } + }, + "Copy Bundle ID" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy Bundle ID" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制应用ID" + } + } + } + }, + "Copy ID" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy ID" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制ID" + } + } + } + }, + "Copy Logs" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy Logs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制日志" + } + } + } + }, + "Device Info" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device Info" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备信息" + } + } + } + }, + "Discord Support" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Discord Support" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Discord支持" + } + } + } + }, + "Done" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Done" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "完成" + } + } + } + }, + "Download LocalDevVPN" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download LocalDevVPN" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "下载LocalDevVPN(外区)" + } + } + } + }, + "Enable JIT" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable JIT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用JIT" + } + } + } + }, + "Export Failed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Export Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出失败" + } + } + } + }, + "Export Logs" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Export Logs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出日志" + } + } + } + }, + "Failed to Add Profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Add Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加配置失败" + } + } + } + }, + "Failed to Debug App" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Debug App" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "调试软件失败" + } + } + } + }, + "Failed to Remove Profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Remove Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除配置失败" + } + } + } + }, + "Favorite" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favorite" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收藏" + } + } + } + }, + "Favorites" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favorites" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收藏" + } + } + } + }, + "Filter logs" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filter logs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "过滤日志" + } + } + } + }, + "Help" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Help" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "帮助" + } + } + } + }, + "Hide older profiles" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hide older profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏旧配置" + } + } + } + }, + "Home" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Home" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "主页" + } + } + } + }, + "Import Pairing File" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Import Pairing File" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入配对文件" + } + } + } + }, + "JIT" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "JIT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "即时编译" + } + } + } + }, + "Kill" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kill" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终止" + } + } + } + }, + "Launch" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动" + } + } + } + }, + "Launch Apps" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch Apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动应用" + } + } + } + }, + "Launch failed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动失败" + } + } + } + }, + "Launch request sent" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch request sent" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动请求已发送" + } + } + } + }, + "Live device logs" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Live device logs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "实时设备日志" + } + } + } + }, + "Loading..." : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading..." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "加载中..." + } + } + } + }, + "Location Simulation" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Location Simulation" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "虚拟定位" + } + } + } + }, + "Logs Copied" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logs Copied" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志已复制" + } + } + } + }, + "Manage and run JS scripts" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage and run JS scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理&运行JS脚本" + } + } + } + }, + "Most Recent Profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most Recent Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最新配置" + } + } + } + }, + "Name" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名字" + } + } + } + }, + "No Apps Found" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Apps Found" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "找不到应用" + } + } + } + }, + "No Bookmarks" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Bookmarks" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "找不到书签" + } + } + } + }, + "No JIT Apps Found" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No JIT Apps Found" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "找不到适用于JIT的软件" + } + } + } + }, + "No matches" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No matches" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无匹配" + } + } + } + }, + "No matching apps" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No matching apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无匹配软件" + } + } + } + }, + "OK" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "好" + } + } + } + }, + "Other" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Other" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他" + } + } + } + }, + "Other Profiles" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Other Profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他配置" + } + } + } + }, + "Overview" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Overview" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总览" + } + } + } + }, + "Pairing File" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pairing File" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配对文件" + } + } + } + }, + "Pairing File Guide" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pairing File Guide" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配对文件指导" + } + } + } + }, + "Pause" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停" + } + } + } + }, + "Pin" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pin" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选定地点" + } + } + } + }, + "Pinned" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pinned" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已固定" + } + } + } + }, + "Play Route" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Play Route" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用路线" + } + } + } + }, + "Process Inspector" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Process Inspector" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进程查看器" + } + } + } + }, + "Processes" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Processes" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进程" + } + } + } + }, + "Profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" + } + } + } + }, + "Recents" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recents" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近访问" + } + } + } + }, + "Redownload" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redownload" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载" + } + } + } + }, + "Redownload DDI" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redownload DDI" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载开发者镜像" + } + } + } + }, + "Refresh" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Refresh" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "刷新" + } + } + } + }, + "Syslog Speed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Syslog Speed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统日志速度" + } + } + } + }, + "Search apps or bundle ID": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search apps or bundle ID" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索应用或应用ID" + } + } + } + }, + "Reset Script": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reset Script" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置脚本" + } + } + } + }, + "Tools": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tools" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工具" + } + } + } + }, + "Target Device IP": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Target Device IP" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标设备IP" + } + } + } + }, + "Star on GitHub": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Star on Github" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "去 Github 点星标" + } + } + } + }, + "Silent Audio": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Silent Audio" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无声音频" + } + } + } + }, + "Start syslog relay": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start syslog relay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动系统日志输出" + } + } + } + }, + "Simulation Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulation Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "模拟失败" + } + } + } + }, + "Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "无法模拟位置(错误 %d)。请确保设备已连接且DDI已挂载。" + } + } + } + }, + "Route Simulation Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Route Simulation Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "路线模拟失败" + } + } + } + }, + "Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "无法启动路线模拟(错误 %d)。请确保设备已连接且DDI已挂载。" + } + } + } + }, + "Resume syslog stream": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume syslog stream" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复系统日志输出" + } + } + } + }, + "Pause syslog stream": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause syslog stream" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停系统日志输出" + } + } + } + }, + "Copy All (Text)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy All (Text)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "复制全部(文本)" + } + } + } + }, + "Copy All (CSV)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy All (CSV)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "复制全部(CSV)" + } + } + } + }, + "Share…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "分享…" + } + } + } + }, + "Show %d older profiles": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Show %d older profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "显示 %d 个旧配置" + } + } + } + }, + "Access additional tools": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Access additional tools" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "访问其他工具" + } + } + } + }, + "Check app expiration date, install/remove profiles": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check app expiration date, install/remove profiles" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查应用过期日期,并安装/删除配置" + } + } + } + }, + "Check app expiration dates": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check app expiration dates" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查应用过期日期" + } + } + } + }, + "Dashboard overview": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dashboard overview" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仪表板概览" + } + } + } + }, + "DDI files refreshed successfully.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DDI files refreshed successfully." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开发者镜像刷新成功。" + } + } + } + }, + "Existing DDI files will be removed before downloading fresh copies.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Existing DDI files will be removed before downloading fresh copies." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "现有开发者镜像将在下载新副本之前被删除。" + } + } + } + }, + "Failed to redownload DDI files: %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to redownload DDI files: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载开发者镜像失败:%@" + } + } + } + }, + "Imported successfully": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imported successfully" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入成功" + } + } + } + }, + "Inspect running apps": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inspect running apps" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查运行中的应用" + } + } + } + }, + "Location Sim": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Location Sim" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "虚拟定位" + } + } + } + }, + "Manage automation scripts": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage automation scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理自动化脚本" + } + } + } + }, + "Non TXM": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non TXM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "非TXM" + } + } + } + }, + "Plays inaudible audio so iOS keeps the app running.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plays inaudible audio so iOS keeps the app running." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "播放无声音频以保持应用运行。" + } + } + } + }, + "Preparing download…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preparing download…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "准备下载…" + } + } + } + }, + "Processing pairing file…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Processing pairing file…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "处理配对文件…" + } + } + } + }, + "Redownload DDI Files?": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redownload DDI Files?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新下载开发者镜像?" + } + } + } + }, + "Settings is fixed as the 4th tab.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings is fixed as the 4th tab." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置已经固定为第4个标签。" + } + } + } + }, + "Sideload only": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sideload only" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅侧载" + } + } + } + }, + "Simulate GPS location": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate GPS location" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟GPS位置" + } + } + } + }, + "Tab Bar": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tab Bar" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标签栏" + } + } + } + }, + "Treats device as TXM-capable to bypass hardware checks.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Treats device as TXM-capable to bypass hardware checks." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将设备视为支持TXM以绕过硬件检查。" + } + } + } + }, + "TXM": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM" + } + } + } + }, + "TXM (Override)": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM (Override)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TXM(覆盖)" + } + } + } + }, + "Uses low-accuracy location to stay alive when an activity needs it.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uses low-accuracy location to stay alive when an activity needs it." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在需要时使用低精度定位保活。" + } + } + } + }, + "Version %@ • iOS %@ • %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Version %@ • iOS %@ • %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本 %@ • iOS %@ • %@" + } + } + } + }, + "View detailed device metadata": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "View detailed device metadata" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看详细设备元数据" + } + } + } + }, + "Search scripts…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search scripts…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索脚本…" + } + } + } + }, + "Scripts": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scripts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "脚本" + } + } + } + }, + "Save": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + } + } + }, + "System": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "System" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系统" + } + } + } + }, + "Settings": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" + } + } + } + }, + "Search device info…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search device info…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索设备信息…" + } + } + } + }, + "Total Processes": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total Processes" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总进程数量" + } + } + } + }, + "Search location...": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search location…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索地址…" + } + } + } + }, + "Expires: %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expires: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在%@之后过期" + } + } + } + }, + "Simulate Route": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate Route" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟路线" + } + } + } + }, + "Use Route": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Use Route" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用路线" + } + } + } + }, + "Start": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "起点" + } + } + } + }, + "End": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "End" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终点" + } + } + } + }, + "Search for a start and destination to build the route.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search for a start and destination to build the route." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索起终点以建立路线" + } + } + } + }, + "Resolving location…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resolving location…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "建立路线中…" + } + } + } + }, + "Simulate Location": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simulate Location" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模拟定位" + } + } + } + }, + "Stop": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止" + } + } + } + }, + "Tap map to drop pin": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tap map to drop pin" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击地图来固定位置" + } + } + } + }, + "Drop a pin on the map and tap the bookmark icon to save a location.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Drop a pin on the map and tap the bookmark icon to save a location." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在地图上标记地点以保存位置" + } + } + } + }, + "Copied": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copied" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已复制" + } + } + } + }, + "Search": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索" + } + } + } + } + }, + "version" : "1.1" +} \ No newline at end of file diff --git a/StikJIT/Utilities/DeviceInfoManager.swift b/StikJIT/Utilities/DeviceInfoManager.swift index 51201706..c7307bee 100644 --- a/StikJIT/Utilities/DeviceInfoManager.swift +++ b/StikJIT/Utilities/DeviceInfoManager.swift @@ -253,13 +253,13 @@ struct DeviceInfoView: View { Menu { Button { copyAllText() } label: { - Label("Copy All (Text)", systemImage: "doc.on.doc") + Label(NSLocalizedString("Copy All (Text)", comment: ""), systemImage: "doc.on.doc") } Button { copyAllCSV() } label: { - Label("Copy All (CSV)", systemImage: "tablecells") + Label(NSLocalizedString("Copy All (CSV)", comment: ""), systemImage: "tablecells") } Button { shareAll() } label: { - Label("Share…", systemImage: "square.and.arrow.up.on.square") + Label(NSLocalizedString("Share…", comment: ""), systemImage: "square.and.arrow.up.on.square") } } label: { Image(systemName: "ellipsis.circle") diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index ac592e83..a21a2a40 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -534,9 +534,9 @@ struct ConsoleLogsView: View { private var syslogControlLabel: String { if !systemLogStream.isStreaming { - return "Start syslog relay" + return NSLocalizedString("Start syslog relay", comment: "") } - return systemLogStream.isPaused ? "Resume syslog stream" : "Pause syslog stream" + return systemLogStream.isPaused ? NSLocalizedString("Resume syslog stream", comment: "") : NSLocalizedString("Pause syslog stream", comment: "") } private func presentAlert(title: String, message: String) { diff --git a/StikJIT/Views/MainTabView.swift b/StikJIT/Views/MainTabView.swift index 84d017c1..42e44708 100644 --- a/StikJIT/Views/MainTabView.swift +++ b/StikJIT/Views/MainTabView.swift @@ -27,13 +27,13 @@ struct MainTabView: View { private var configurableTabs: [TabDescriptor] { let tabs: [TabDescriptor] = [ - TabDescriptor(id: "home", title: "Apps", systemImage: "square.grid.2x2") { AnyView(HomeView()) }, - TabDescriptor(id: "scripts", title: "Scripts", systemImage: "scroll") { AnyView(ScriptListView()) }, - TabDescriptor(id: "tools", title: "Tools", systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, - TabDescriptor(id: "deviceinfo", title: "Device Info", systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, - TabDescriptor(id: "profiles", title: "App Expiry", systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, - TabDescriptor(id: "processes", title: "Processes", systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, - TabDescriptor(id: "location", title: "Location", systemImage: "location") { AnyView(LocationSimulationView()) } + TabDescriptor(id: "home", title: NSLocalizedString("Apps", comment: ""), systemImage: "square.grid.2x2") { AnyView(HomeView()) }, + TabDescriptor(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), systemImage: "scroll") { AnyView(ScriptListView()) }, + TabDescriptor(id: "tools", title: NSLocalizedString("Tools", comment: ""), systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, + TabDescriptor(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, + TabDescriptor(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, + TabDescriptor(id: "processes", title: NSLocalizedString("Processes", comment: ""), systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, + TabDescriptor(id: "location", title: NSLocalizedString("Location", comment: ""), systemImage: "location") { AnyView(LocationSimulationView()) } ] return tabs } @@ -42,7 +42,7 @@ struct MainTabView: View { configurableTabs } - private let settingsTab = TabDescriptor(id: "settings", title: "Settings", systemImage: "gearshape.fill") { + private let settingsTab = TabDescriptor(id: "settings", title: NSLocalizedString("Settings", comment: ""), systemImage: "gearshape.fill") { AnyView(SettingsView()) } diff --git a/StikJIT/Views/MapSelectionView.swift b/StikJIT/Views/MapSelectionView.swift index b787e583..7ef26232 100644 --- a/StikJIT/Views/MapSelectionView.swift +++ b/StikJIT/Views/MapSelectionView.swift @@ -804,9 +804,9 @@ struct LocationSimulationView: View { private func simulate() { guard pairingExists, let coord = coordinate, !isBusy else { return } runLocationCommand( - errorTitle: "Simulation Failed", + errorTitle: NSLocalizedString("Simulation Failed", comment: ""), errorMessage: { code in - "Could not simulate location (error \(code)). Make sure the device is connected and the DDI is mounted." + String(format: NSLocalizedString("Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) }, operation: { locationUpdateCode(for: coord) } ) { @@ -827,9 +827,9 @@ struct LocationSimulationView: View { stopResendLoop() cancelRoutePlayback(resetMarker: false) runLocationCommand( - errorTitle: "Route Simulation Failed", + errorTitle: NSLocalizedString("Route Simulation Failed", comment: ""), errorMessage: { code in - "Could not start route simulation (error \(code)). Make sure the device is connected and the DDI is mounted." + String(format: NSLocalizedString("Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) }, operation: { locationUpdateCode(for: firstCoordinate) } ) { @@ -1137,7 +1137,7 @@ private struct RouteSearchSheet: View { NavigationStack { VStack(alignment: .leading, spacing: 16) { routeField( - title: "Start", + title: NSLocalizedString("Start", comment: ""), icon: "circle.fill", tint: .green, text: $startQuery, @@ -1146,7 +1146,7 @@ private struct RouteSearchSheet: View { ) routeField( - title: "End", + title: NSLocalizedString("End", comment: ""), icon: "flag.checkered.circle.fill", tint: .red, text: $endQuery, diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 2f8e948e..008f6912 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -158,7 +158,7 @@ struct ProfileView: View { if let match = entry.bestMatchingProfile { HStack { Image(systemName: "clock") - Text("Expires: \(match.profile.formattedDate)") + Text(String(format: NSLocalizedString("Expires: %@", comment: ""), match.profile.formattedDate)) } .foregroundStyle(match.profile.dateColor) .font(.subheadline) @@ -203,7 +203,7 @@ struct ProfileView: View { else { expandedApps.insert(entry.id) } } } label: { - Label(showMore ? "Hide older profiles" : "Show \(extraProfiles.count) older profiles", + Label(showMore ? NSLocalizedString("Hide older profiles", comment: "") : String(format: NSLocalizedString("Show %d older profiles", comment: ""), extraProfiles.count), systemImage: showMore ? "chevron.up" : "chevron.down") .font(.caption) .foregroundStyle(.blue) diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 83f0cb37..261c772b 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -46,14 +46,14 @@ struct SettingsView: View { private var tabOptions: [TabOption] { var options: [TabOption] = [ - TabOption(id: "home", title: "Home", detail: "Dashboard overview", icon: "house", isBeta: false), - TabOption(id: "scripts", title: "Scripts", detail: "Manage automation scripts", icon: "scroll", isBeta: false), - TabOption(id: "tools", title: "Tools", detail: "Access additional tools", icon: "wrench.and.screwdriver", isBeta: false) + TabOption(id: "home", title: NSLocalizedString("Home", comment: ""), detail: NSLocalizedString("Dashboard overview", comment: ""), icon: "house", isBeta: false), + TabOption(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage automation scripts", comment: ""), icon: "scroll", isBeta: false), + TabOption(id: "tools", title: NSLocalizedString("Tools", comment: ""), detail: NSLocalizedString("Access additional tools", comment: ""), icon: "wrench.and.screwdriver", isBeta: false) ] - options.append(TabOption(id: "deviceinfo", title: "Device Info", detail: "View detailed device metadata", icon: "iphone.and.arrow.forward", isBeta: false)) - options.append(TabOption(id: "profiles", title: "App Expiry", detail: "Check app expiration date, install/remove profiles", icon: "calendar.badge.clock", isBeta: false)) - options.append(TabOption(id: "processes", title: "Processes", detail: "Inspect running apps", icon: "rectangle.stack.person.crop", isBeta: false)) - options.append(TabOption(id: "location", title: "Location Sim", detail: "Sideload only", icon: "location", isBeta: false)) + options.append(TabOption(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), icon: "iphone.and.arrow.forward", isBeta: false)) + options.append(TabOption(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration date, install/remove profiles", comment: ""), icon: "calendar.badge.clock", isBeta: false)) + options.append(TabOption(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), icon: "rectangle.stack.person.crop", isBeta: false)) + options.append(TabOption(id: "location", title: NSLocalizedString("Location Sim", comment: ""), detail: NSLocalizedString("Sideload only", comment: ""), icon: "location", isBeta: false)) return options } @@ -80,17 +80,17 @@ struct SettingsView: View { // 2) GitHub Section { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug/stargazers")!) { - Label("Star on GitHub", systemImage: "star") + Label(NSLocalizedString("Star on GitHub", comment: ""), systemImage: "star") } } // 3) Pairing File - Section("Pairing File") { + Section(NSLocalizedString("Pairing File", comment: "")) { Button { isShowingPairingFilePicker = true } label: { - Label("Import Pairing File", systemImage: "doc.badge.plus") + Label(NSLocalizedString("Import Pairing File", comment: ""), systemImage: "doc.badge.plus") } if showPairingFileMessage && !isImportingFile { - Label("Imported successfully", systemImage: "checkmark.circle.fill") + Label(NSLocalizedString("Imported successfully", comment: ""), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } } @@ -99,8 +99,8 @@ struct SettingsView: View { Section { Toggle(isOn: $keepAliveAudio) { VStack(alignment: .leading, spacing: 2) { - Text("Silent Audio") - Text("Plays inaudible audio so iOS keeps the app running.") + Text(NSLocalizedString("Silent Audio", comment: "")) + Text(NSLocalizedString("Plays inaudible audio so iOS keeps the app running.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } @@ -111,8 +111,8 @@ struct SettingsView: View { Toggle(isOn: $keepAliveLocation) { VStack(alignment: .leading, spacing: 2) { - Text("Background Location") - Text("Uses low-accuracy location to stay alive when an activity needs it.") + Text(NSLocalizedString("Background Location", comment: "")) + Text(NSLocalizedString("Uses low-accuracy location to stay alive when an activity needs it.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } @@ -121,24 +121,24 @@ struct SettingsView: View { } } header: { - Text("Background Keep-Alive") + Text(NSLocalizedString("Background Keep-Alive", comment: "")) } // 6) Behavior - Section("Behavior") { + Section(NSLocalizedString("Behavior", comment: "")) { Toggle(isOn: $overrideTXMDetection) { VStack(alignment: .leading, spacing: 2) { - Text("Always Run Scripts") - Text("Treats device as TXM-capable to bypass hardware checks.") + Text(NSLocalizedString("Always Run Scripts", comment: "")) + Text(NSLocalizedString("Treats device as TXM-capable to bypass hardware checks.", comment: "")) .font(.caption).foregroundStyle(.secondary) } } } // 7) Advanced - Section("Advanced") { + Section(NSLocalizedString("Advanced", comment: "")) { HStack { - Text("Target Device IP") + Text(NSLocalizedString("Target Device IP", comment: "")) Spacer() TextField("10.7.0.1", text: $customTargetIP) .multilineTextAlignment(.trailing) @@ -146,10 +146,10 @@ struct SettingsView: View { .submitLabel(.done) } Button { openAppFolder() } label: { - Label("App Folder", systemImage: "folder") + Label(NSLocalizedString("App Folder", comment: ""), systemImage: "folder") }.foregroundStyle(.primary) Button { showDDIConfirmation = true } label: { - Label("Redownload DDI", systemImage: "arrow.down.circle") + Label(NSLocalizedString("Redownload DDI", comment: ""), systemImage: "arrow.down.circle") }.foregroundStyle(.primary).disabled(isRedownloadingDDI) if isRedownloadingDDI { VStack(alignment: .leading, spacing: 4) { @@ -162,15 +162,15 @@ struct SettingsView: View { } // 7) Help - Section("Help") { + Section(NSLocalizedString("Help", comment: "")) { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug-Guide/blob/main/pairing_file.md")!) { - Label("Pairing File Guide", systemImage: "questionmark.circle") + Label(NSLocalizedString("Pairing File Guide", comment: ""), systemImage: "questionmark.circle") } Link(destination: URL(string: "https://apps.apple.com/us/app/localdevvpn/id6755608044")!) { - Label("Download LocalDevVPN", systemImage: "arrow.down.circle") + Label(NSLocalizedString("Download LocalDevVPN", comment: ""), systemImage: "arrow.down.circle") } Link(destination: URL(string: "https://discord.gg/qahjXNTDwS")!) { - Label("Discord Support", systemImage: "bubble.left.and.bubble.right") + Label(NSLocalizedString("Discord Support", comment: ""), systemImage: "bubble.left.and.bubble.right") } } @@ -182,7 +182,7 @@ struct SettingsView: View { .listRowBackground(Color.clear) } } - .navigationTitle("Settings") + .navigationTitle(NSLocalizedString("Settings", comment: "")) } .fileImporter( isPresented: $isShowingPairingFilePicker, @@ -225,13 +225,13 @@ struct SettingsView: View { break } } - .confirmationDialog("Redownload DDI Files?", isPresented: $showDDIConfirmation, titleVisibility: .visible) { - Button("Redownload", role: .destructive) { + .confirmationDialog(NSLocalizedString("Redownload DDI Files?", comment: ""), isPresented: $showDDIConfirmation, titleVisibility: .visible) { + Button(NSLocalizedString("Redownload", comment: ""), role: .destructive) { redownloadDDIPressed() } - Button("Cancel", role: .cancel) { } + Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) { } } message: { - Text("Existing DDI files will be removed before downloading fresh copies.") + Text(NSLocalizedString("Existing DDI files will be removed before downloading fresh copies.", comment: "")) } .overlay { if isImportingFile { importBusyOverlay } } } @@ -240,7 +240,7 @@ struct SettingsView: View { private var importBusyOverlay: some View { Color.black.opacity(0.35).ignoresSafeArea() VStack(spacing: 12) { - ProgressView("Processing pairing file…") + ProgressView(NSLocalizedString("Processing pairing file…", comment: "")) VStack(spacing: 8) { GeometryReader { geometry in ZStack(alignment: .leading) { @@ -276,11 +276,11 @@ struct SettingsView: View { let processInfo = ProcessInfo.processInfo let txmLabel: String if processInfo.isTXMOverridden { - txmLabel = "TXM (Override)" + txmLabel = NSLocalizedString("TXM (Override)", comment: "") } else { - txmLabel = processInfo.hasTXM ? "TXM" : "Non TXM" + txmLabel = processInfo.hasTXM ? NSLocalizedString("TXM", comment: "") : NSLocalizedString("Non TXM", comment: "") } - return "Version \(appVersion) • iOS \(UIDevice.current.systemVersion) • \(txmLabel)" + return String(format: NSLocalizedString("Version %@ • iOS %@ • %@", comment: ""), appVersion, UIDevice.current.systemVersion, txmLabel) } // MARK: - Business Logic @@ -299,7 +299,7 @@ struct SettingsView: View { await MainActor.run { isRedownloadingDDI = true ddiDownloadProgress = 0 - ddiStatusMessage = "Preparing download…" + ddiStatusMessage = NSLocalizedString("Preparing download…", comment: "") ddiResultMessage = nil } do { @@ -368,13 +368,13 @@ struct TabCustomizationView: View { enabledTabIdentifiers = TabConfiguration.serialize(ids) } } header: { - Text("Pinned") + Text(NSLocalizedString("Pinned", comment: "")) } footer: { - Text("Settings is fixed as the 4th tab.") + Text(NSLocalizedString("Settings is fixed as the 4th tab.", comment: "")) } if !availableOptions.isEmpty { - Section("Available") { + Section(NSLocalizedString("Available", comment: "")) { ForEach(availableOptions) { option in Button { var ids = selectedIDs @@ -391,7 +391,7 @@ struct TabCustomizationView: View { } } } - .navigationTitle("Tab Bar") + .navigationTitle(NSLocalizedString("Tab Bar", comment: "")) .toolbar { EditButton() } diff --git a/StikJIT/Views/ToolsView.swift b/StikJIT/Views/ToolsView.swift index bf274bb9..8b2f548b 100644 --- a/StikJIT/Views/ToolsView.swift +++ b/StikJIT/Views/ToolsView.swift @@ -18,12 +18,12 @@ struct ToolsView: View { private var tools: [ToolItem] { [ - ToolItem(id: "scripts", title: "Scripts", detail: "Manage and run JS scripts", systemImage: "scroll", destination: AnyView(ScriptListView())), - ToolItem(id: "console", title: "Console", detail: "Live device logs", systemImage: "terminal", destination: AnyView(ConsoleLogsView())), - ToolItem(id: "deviceinfo", title: "Device Info", detail: "View detailed device metadata", systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), - ToolItem(id: "profiles", title: "App Expiry", detail: "Check app expiration dates", systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), - ToolItem(id: "processes", title: "Processes", detail: "Inspect running apps", systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), - ToolItem(id: "location", title: "Location Simulation", detail: "Simulate GPS location", systemImage: "location", destination: AnyView(LocationSimulationView())) + ToolItem(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage and run JS scripts", comment: ""), systemImage: "scroll", destination: AnyView(ScriptListView())), + ToolItem(id: "console", title: NSLocalizedString("Console", comment: ""), detail: NSLocalizedString("Live device logs", comment: ""), systemImage: "terminal", destination: AnyView(ConsoleLogsView())), + ToolItem(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), + ToolItem(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration dates", comment: ""), systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), + ToolItem(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), + ToolItem(id: "location", title: NSLocalizedString("Location Simulation", comment: ""), detail: NSLocalizedString("Simulate GPS location", comment: ""), systemImage: "location", destination: AnyView(LocationSimulationView())) ] } @@ -45,7 +45,7 @@ struct ToolsView: View { } } } - .navigationTitle("Tools") + .navigationTitle(NSLocalizedString("Tools", comment: "")) } } } From 83e4cfb89b702cfe731e37ebe46b212ca130b7e5 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Wed, 13 May 2026 22:35:03 +0800 Subject: [PATCH 12/14] Bumped translation to 3.1.2, Changed code localized format to desired ones --- StikJIT/Assets.xcassets/Localizable.xcstrings | 524 +++++++++++++++++- StikJIT/Utilities/DeviceInfoManager.swift | 20 +- StikJIT/Views/ConsoleLogsView.swift | 4 +- StikJIT/Views/MainTabView.swift | 16 +- StikJIT/Views/MapSelectionView.swift | 12 +- StikJIT/Views/ProfileView.swift | 6 +- StikJIT/Views/SettingsView.swift | 74 +-- StikJIT/Views/ToolsView.swift | 14 +- 8 files changed, 573 insertions(+), 97 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 8ba8bb1e..aa16a69c 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -834,36 +834,36 @@ } } }, - "Launch failed" : { + "Launch failed for %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Launch failed" + "value" : "Launch failed for %@" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "启动失败" + "value" : "启动 %@ 失败" } } } }, - "Launch request sent" : { + "Launch request sent for %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Launch request sent" + "value" : "Launch request sent for %@" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "启动请求已发送" + "value" : "启动请求已发送至 %@" } } } @@ -1905,23 +1905,6 @@ } } }, - "Processing pairing file…": { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Processing pairing file…" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "处理配对文件…" - } - } - } - }, "Redownload DDI Files?": { "extractionState" : "manual", "localizations" : { @@ -2257,7 +2240,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "在%@之后过期" + "value" : "在 %@ 之后过期" } } } @@ -2465,6 +2448,499 @@ } } } + }, + "Error Occurred While Executing Script.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error Occurred While Executing Script." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "执行脚本时发生错误。" + } + } + } + }, + "Starting JIT for %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Starting JIT for %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在为 %@ 启动即时编译" + } + } + } + }, + "JIT request completed for %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "JIT request completed for %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 的即时编译请求已完成" + } + } + } + }, + "JIT failed for %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "JIT failed for %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 即时编译失败" + } + } + } + }, + "StikDebug could not launch or attach to the selected app. Check that the VPN is enabled, the pairing file is current, and the app is still installed.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "StikDebug could not launch or attach to the selected app. Check that the VPN is enabled, the pairing file is current, and the app is still installed." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "StikDebug 无法启动或附加到选定的应用。请检查VPN是否启用,配对文件是否最新,以及应用是否仍然存在。" + } + } + } + }, + "Failed to Enable JIT": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Enable JIT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法启用 JIT" + } + } + } + }, + "Either bundle ID or PID should be specified.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Either bundle ID or PID should be specified." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用ID 或 PID 应该被制定。" + } + } + } + }, + "Enable JIT for %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable JIT for %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "为 %@ 启用即时编译" + } + } + } + }, + "Double-tap to open the app and enable JIT. Use the actions rotor for favorites or bundle ID.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Double-tap to open the app and enable JIT. Use the actions rotor for favorites or bundle ID." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "双击以启动应用或给应用启用即时编译。请使用操作转子来访问收藏或应用ID" + } + } + } + }, + "Bundle ID %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bundle ID %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用ID %@" + } + } + } + }, + "Assigned script %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assigned script %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已分配脚本 %@" + } + } + } + }, + "Remove from Favorites": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove from Favorites" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从收藏中移除" + } + } + } + }, + "Favorites are full": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favorites are full" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收藏已满" + } + } + } + }, + "Removed from Favorites" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Removed from Favorites" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已从收藏中移除" + } + } + } + }, + "Bundle ID copied": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bundle ID copied" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已复制应用ID" + } + } + } + }, + "Launch %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动 %@" + } + } + } + }, + "Launch request in progress.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launch request in progress." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在请求启动。" + } + } + } + }, + "Double-tap to launch this app without enabling JIT.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Double-tap to launch this app without enabling JIT." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "双击以启动应用但不启用即时编译。" + } + } + } + }, + "Launching" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Launching" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动中" + } + } + } + }, + "Ready": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ready" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "就绪" + } + } + } + }, + "Remove profile for %@ (UUID: %@)?\nApps associated with this profile may become unavailable.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove profile for %@ (UUID: %@)?\nApps associated with this profile may become unavailable." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "要删除 %@(UUID:%@)的配置吗?\n与此配置相关联的应用可能无法再使用。" + } + } + } + }, + "Fetching device info…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fetching device info…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取设备信息中…" + } + } + } + }, + "No pairing file detected": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No pairing file detected" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到配对文件" + } + } + } + }, + "Import your device's pairing file to get started.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Import your device's pairing file to get started." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入设备的配对文件以开始使用。" + } + } + } + }, + "Copy Value": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy Value" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制数值" + } + } + } + }, + "Copy Key & Value": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy Key & Value" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制键和值" + } + } + } + }, + "Export": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Export" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出" + } + } + } + }, + "Remove Favorite": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove Favorite" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除收藏" + } + } + } + }, + "Processing pairing file…": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Processing pairing file…" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "处理配对文件中…" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/Utilities/DeviceInfoManager.swift b/StikJIT/Utilities/DeviceInfoManager.swift index c7307bee..3c21ec8c 100644 --- a/StikJIT/Utilities/DeviceInfoManager.swift +++ b/StikJIT/Utilities/DeviceInfoManager.swift @@ -164,7 +164,7 @@ struct DeviceInfoView: View { List { if !isPaired { Section { - Label("No pairing file detected", systemImage: "exclamationmark.triangle.fill") + Label(String(format: "No pairing file detected".localized), systemImage: "exclamationmark.triangle.fill") .foregroundStyle(.orange) Text("Import your device's pairing file to get started.") .font(.footnote) @@ -186,10 +186,10 @@ struct DeviceInfoView: View { .padding(.vertical, 2) .contextMenu { Button { copyToPasteboard(entry.value) } label: { - Label("Copy Value", systemImage: "doc.on.doc") + Label(String(format: "Copy Value".localized), systemImage: "doc.on.doc") } Button { copyToPasteboard("\(entry.key): \(entry.value)") } label: { - Label("Copy Key & Value", systemImage: "doc.on.clipboard") + Label(String(format: "Copy Key & Value".localized), systemImage: "doc.on.clipboard") } } } @@ -210,7 +210,7 @@ struct DeviceInfoView: View { .overlay { if mgr.busy { Color.black.opacity(0.35).ignoresSafeArea() - ProgressView("Fetching device info…") + ProgressView(String(format: "Fetching device info…".localized)) .padding(16) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) } @@ -236,7 +236,7 @@ struct DeviceInfoView: View { ToolbarItemGroup(placement: .navigationBarTrailing) { if isPaired { Button { mgr.initAndLoad() } label: { - Label("Reload", systemImage: "arrow.clockwise") + Label(String(format: "Reload".localized), systemImage: "arrow.clockwise") } Button { @@ -247,19 +247,19 @@ struct DeviceInfoView: View { fail("Export Failed", error.localizedDescription) } } label: { - Label("Export", systemImage: "square.and.arrow.up") + Label(String(format: "Export".localized), systemImage: "square.and.arrow.up") } .disabled(mgr.entries.isEmpty) Menu { Button { copyAllText() } label: { - Label(NSLocalizedString("Copy All (Text)", comment: ""), systemImage: "doc.on.doc") + Label(String(format: "Copy All (Text)".localized), systemImage: "doc.on.doc") } Button { copyAllCSV() } label: { - Label(NSLocalizedString("Copy All (CSV)", comment: ""), systemImage: "tablecells") + Label(String(format: "Copy All (CSV)".localized), systemImage: "tablecells") } Button { shareAll() } label: { - Label(NSLocalizedString("Share…", comment: ""), systemImage: "square.and.arrow.up.on.square") + Label(String(format: "Share…".localized), systemImage: "square.and.arrow.up.on.square") } } label: { Image(systemName: "ellipsis.circle") @@ -270,7 +270,7 @@ struct DeviceInfoView: View { ToolbarItem(placement: .navigationBarLeading) { if !isPaired { Button { importer = true } label: { - Label("Import Pairing File", systemImage: "doc.badge.plus") + Label(String(format: "Import Pairing File".localized), systemImage: "doc.badge.plus") } } } diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index a21a2a40..7e5e1683 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -534,9 +534,9 @@ struct ConsoleLogsView: View { private var syslogControlLabel: String { if !systemLogStream.isStreaming { - return NSLocalizedString("Start syslog relay", comment: "") + return String(format: "Start syslog relay".localized) } - return systemLogStream.isPaused ? NSLocalizedString("Resume syslog stream", comment: "") : NSLocalizedString("Pause syslog stream", comment: "") + return systemLogStream.isPaused ? String(format: "Resume syslog stream".localized) : String(format: "Pause syslog stream".localized) } private func presentAlert(title: String, message: String) { diff --git a/StikJIT/Views/MainTabView.swift b/StikJIT/Views/MainTabView.swift index 42e44708..6d579c27 100644 --- a/StikJIT/Views/MainTabView.swift +++ b/StikJIT/Views/MainTabView.swift @@ -27,13 +27,13 @@ struct MainTabView: View { private var configurableTabs: [TabDescriptor] { let tabs: [TabDescriptor] = [ - TabDescriptor(id: "home", title: NSLocalizedString("Apps", comment: ""), systemImage: "square.grid.2x2") { AnyView(HomeView()) }, - TabDescriptor(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), systemImage: "scroll") { AnyView(ScriptListView()) }, - TabDescriptor(id: "tools", title: NSLocalizedString("Tools", comment: ""), systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, - TabDescriptor(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, - TabDescriptor(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, - TabDescriptor(id: "processes", title: NSLocalizedString("Processes", comment: ""), systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, - TabDescriptor(id: "location", title: NSLocalizedString("Location", comment: ""), systemImage: "location") { AnyView(LocationSimulationView()) } + TabDescriptor(id: "home", title: String(format: "Apps".localized), systemImage: "square.grid.2x2") { AnyView(HomeView()) }, + TabDescriptor(id: "scripts", title: String(format: "Scripts".localized), systemImage: "scroll") { AnyView(ScriptListView()) }, + TabDescriptor(id: "tools", title: String(format: "Tools".localized), systemImage: "wrench.and.screwdriver") { AnyView(ToolsView()) }, + TabDescriptor(id: "deviceinfo", title: String(format: "Device Info".localized), systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) }, + TabDescriptor(id: "profiles", title: String(format: "App Expiry".localized), systemImage: "calendar.badge.clock") { AnyView(ProfileView()) }, + TabDescriptor(id: "processes", title: String(format: "Processes".localized), systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }, + TabDescriptor(id: "location", title: String(format: "Location".localized), systemImage: "location") { AnyView(LocationSimulationView()) } ] return tabs } @@ -42,7 +42,7 @@ struct MainTabView: View { configurableTabs } - private let settingsTab = TabDescriptor(id: "settings", title: NSLocalizedString("Settings", comment: ""), systemImage: "gearshape.fill") { + private let settingsTab = TabDescriptor(id: "settings", title: String(format: "Settings".localized), systemImage: "gearshape.fill") { AnyView(SettingsView()) } diff --git a/StikJIT/Views/MapSelectionView.swift b/StikJIT/Views/MapSelectionView.swift index 7ef26232..a881f982 100644 --- a/StikJIT/Views/MapSelectionView.swift +++ b/StikJIT/Views/MapSelectionView.swift @@ -804,9 +804,9 @@ struct LocationSimulationView: View { private func simulate() { guard pairingExists, let coord = coordinate, !isBusy else { return } runLocationCommand( - errorTitle: NSLocalizedString("Simulation Failed", comment: ""), + errorTitle: String(format: "Simulation Failed".localized), errorMessage: { code in - String(format: NSLocalizedString("Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) + String(format: "Could not simulate location (error %d). Make sure the device is connected and the DDI is mounted.".localized, code) }, operation: { locationUpdateCode(for: coord) } ) { @@ -827,9 +827,9 @@ struct LocationSimulationView: View { stopResendLoop() cancelRoutePlayback(resetMarker: false) runLocationCommand( - errorTitle: NSLocalizedString("Route Simulation Failed", comment: ""), + errorTitle: String(format: "Route Simulation Failed".localized), errorMessage: { code in - String(format: NSLocalizedString("Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.", comment: ""), code) + String(format: "Could not start route simulation (error %d). Make sure the device is connected and the DDI is mounted.".localized, code) }, operation: { locationUpdateCode(for: firstCoordinate) } ) { @@ -1137,7 +1137,7 @@ private struct RouteSearchSheet: View { NavigationStack { VStack(alignment: .leading, spacing: 16) { routeField( - title: NSLocalizedString("Start", comment: ""), + title: String(format: "Start".localized), icon: "circle.fill", tint: .green, text: $startQuery, @@ -1146,7 +1146,7 @@ private struct RouteSearchSheet: View { ) routeField( - title: NSLocalizedString("End", comment: ""), + title: String(format: "End".localized), icon: "flag.checkered.circle.fill", tint: .red, text: $endQuery, diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 008f6912..0af20d00 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -158,7 +158,7 @@ struct ProfileView: View { if let match = entry.bestMatchingProfile { HStack { Image(systemName: "clock") - Text(String(format: NSLocalizedString("Expires: %@", comment: ""), match.profile.formattedDate)) + Text(String(format: "Expires: %@".localized, match.profile.formattedDate)) } .foregroundStyle(match.profile.dateColor) .font(.subheadline) @@ -203,7 +203,7 @@ struct ProfileView: View { else { expandedApps.insert(entry.id) } } } label: { - Label(showMore ? NSLocalizedString("Hide older profiles", comment: "") : String(format: NSLocalizedString("Show %d older profiles", comment: ""), extraProfiles.count), + Label(showMore ? String(format: "Hide older profiles".localized) : String(format: "Show %d older profiles".localized, extraProfiles.count), systemImage: showMore ? "chevron.up" : "chevron.down") .font(.caption) .foregroundStyle(.blue) @@ -282,7 +282,7 @@ struct ProfileView: View { } Button("Cancel", role: .cancel) { } } message: { - Text("Remove profile for \(removeTargetName) (UUID: \(removeTargetUUID))?\nApps associated with this profile may become unavailable.") + Text(String(format: "Remove profile for %@ (UUID: %@)?\nApps associated with this profile may become unavailable.".localized, removeTargetName, removeTargetUUID)) } } diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 261c772b..8b122c71 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -46,14 +46,14 @@ struct SettingsView: View { private var tabOptions: [TabOption] { var options: [TabOption] = [ - TabOption(id: "home", title: NSLocalizedString("Home", comment: ""), detail: NSLocalizedString("Dashboard overview", comment: ""), icon: "house", isBeta: false), - TabOption(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage automation scripts", comment: ""), icon: "scroll", isBeta: false), - TabOption(id: "tools", title: NSLocalizedString("Tools", comment: ""), detail: NSLocalizedString("Access additional tools", comment: ""), icon: "wrench.and.screwdriver", isBeta: false) + TabOption(id: "home", title: String("Home".localized), detail: String("Dashboard overview".localized), icon: "house", isBeta: false), + TabOption(id: "scripts", title: String("Scripts".localized), detail: String("Manage automation scripts".localized), icon: "scroll", isBeta: false), + TabOption(id: "tools", title: String("Tools".localized), detail: String("Access additional tools".localized), icon: "wrench.and.screwdriver", isBeta: false) ] - options.append(TabOption(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), icon: "iphone.and.arrow.forward", isBeta: false)) - options.append(TabOption(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration date, install/remove profiles", comment: ""), icon: "calendar.badge.clock", isBeta: false)) - options.append(TabOption(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), icon: "rectangle.stack.person.crop", isBeta: false)) - options.append(TabOption(id: "location", title: NSLocalizedString("Location Sim", comment: ""), detail: NSLocalizedString("Sideload only", comment: ""), icon: "location", isBeta: false)) + options.append(TabOption(id: "deviceinfo", title: String("Device Info".localized), detail: String("View detailed device metadata".localized), icon: "iphone.and.arrow.forward", isBeta: false)) + options.append(TabOption(id: "profiles", title: String("App Expiry".localized), detail: String("Check app expiration date, install/remove profiles".localized), icon: "calendar.badge.clock", isBeta: false)) + options.append(TabOption(id: "processes", title: String("Processes".localized), detail: String("Inspect running apps".localized), icon: "rectangle.stack.person.crop", isBeta: false)) + options.append(TabOption(id: "location", title: String("Location Sim".localized), detail: String("Sideload only".localized), icon: "location", isBeta: false)) return options } @@ -80,17 +80,17 @@ struct SettingsView: View { // 2) GitHub Section { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug/stargazers")!) { - Label(NSLocalizedString("Star on GitHub", comment: ""), systemImage: "star") + Label(String(format: "Star on GitHub".localized), systemImage: "star") } } // 3) Pairing File - Section(NSLocalizedString("Pairing File", comment: "")) { + Section(String(format: "Pairing File".localized)) { Button { isShowingPairingFilePicker = true } label: { - Label(NSLocalizedString("Import Pairing File", comment: ""), systemImage: "doc.badge.plus") + Label(String(format: "Import Pairing File".localized), systemImage: "doc.badge.plus") } if showPairingFileMessage && !isImportingFile { - Label(NSLocalizedString("Imported successfully", comment: ""), systemImage: "checkmark.circle.fill") + Label(String(format: "Imported successfully".localized), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } } @@ -99,8 +99,8 @@ struct SettingsView: View { Section { Toggle(isOn: $keepAliveAudio) { VStack(alignment: .leading, spacing: 2) { - Text(NSLocalizedString("Silent Audio", comment: "")) - Text(NSLocalizedString("Plays inaudible audio so iOS keeps the app running.", comment: "")) + Text(String(format: "Silent Audio".localized)) + Text(String(format: "Plays inaudible audio so iOS keeps the app running.".localized)) .font(.caption).foregroundStyle(.secondary) } } @@ -111,8 +111,8 @@ struct SettingsView: View { Toggle(isOn: $keepAliveLocation) { VStack(alignment: .leading, spacing: 2) { - Text(NSLocalizedString("Background Location", comment: "")) - Text(NSLocalizedString("Uses low-accuracy location to stay alive when an activity needs it.", comment: "")) + Text(String(format: "Background Location".localized)) + Text(String(format: "Uses low-accuracy location to stay alive when an activity needs it.".localized)) .font(.caption).foregroundStyle(.secondary) } } @@ -121,24 +121,24 @@ struct SettingsView: View { } } header: { - Text(NSLocalizedString("Background Keep-Alive", comment: "")) + Text(String(format: "Background Keep-Alive".localized)) } // 6) Behavior - Section(NSLocalizedString("Behavior", comment: "")) { + Section(String(format: "Behavior".localized)) { Toggle(isOn: $overrideTXMDetection) { VStack(alignment: .leading, spacing: 2) { - Text(NSLocalizedString("Always Run Scripts", comment: "")) - Text(NSLocalizedString("Treats device as TXM-capable to bypass hardware checks.", comment: "")) + Text(String(format: "Always Run Scripts".localized)) + Text(String(format: "Treats device as TXM-capable to bypass hardware checks.".localized)) .font(.caption).foregroundStyle(.secondary) } } } // 7) Advanced - Section(NSLocalizedString("Advanced", comment: "")) { + Section(String(format: "Advanced".localized)) { HStack { - Text(NSLocalizedString("Target Device IP", comment: "")) + Text(String(format: "Target Device IP".localized)) Spacer() TextField("10.7.0.1", text: $customTargetIP) .multilineTextAlignment(.trailing) @@ -146,10 +146,10 @@ struct SettingsView: View { .submitLabel(.done) } Button { openAppFolder() } label: { - Label(NSLocalizedString("App Folder", comment: ""), systemImage: "folder") + Label(String(format: "App Folder".localized), systemImage: "folder") }.foregroundStyle(.primary) Button { showDDIConfirmation = true } label: { - Label(NSLocalizedString("Redownload DDI", comment: ""), systemImage: "arrow.down.circle") + Label(String(format: "Redownload DDI".localized), systemImage: "arrow.down.circle") }.foregroundStyle(.primary).disabled(isRedownloadingDDI) if isRedownloadingDDI { VStack(alignment: .leading, spacing: 4) { @@ -162,15 +162,15 @@ struct SettingsView: View { } // 7) Help - Section(NSLocalizedString("Help", comment: "")) { + Section(String(format: "Help".localized)) { Link(destination: URL(string: "https://github.com/StephenDev0/StikDebug-Guide/blob/main/pairing_file.md")!) { - Label(NSLocalizedString("Pairing File Guide", comment: ""), systemImage: "questionmark.circle") + Label(String(format: "Pairing File Guide".localized), systemImage: "questionmark.circle") } Link(destination: URL(string: "https://apps.apple.com/us/app/localdevvpn/id6755608044")!) { - Label(NSLocalizedString("Download LocalDevVPN", comment: ""), systemImage: "arrow.down.circle") + Label(String(format: "Download LocalDevVPN".localized), systemImage: "arrow.down.circle") } Link(destination: URL(string: "https://discord.gg/qahjXNTDwS")!) { - Label(NSLocalizedString("Discord Support", comment: ""), systemImage: "bubble.left.and.bubble.right") + Label(String(format: "Discord Support".localized), systemImage: "bubble.left.and.bubble.right") } } @@ -182,7 +182,7 @@ struct SettingsView: View { .listRowBackground(Color.clear) } } - .navigationTitle(NSLocalizedString("Settings", comment: "")) + .navigationTitle(String(format: "Settings".localized)) } .fileImporter( isPresented: $isShowingPairingFilePicker, @@ -225,13 +225,13 @@ struct SettingsView: View { break } } - .confirmationDialog(NSLocalizedString("Redownload DDI Files?", comment: ""), isPresented: $showDDIConfirmation, titleVisibility: .visible) { - Button(NSLocalizedString("Redownload", comment: ""), role: .destructive) { + .confirmationDialog(String(format: "Redownload DDI Files?".localized), isPresented: $showDDIConfirmation, titleVisibility: .visible) { + Button(String(format: "Redownload".localized), role: .destructive) { redownloadDDIPressed() } - Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) { } + Button(String(format: "Cancel".localized), role: .cancel) { } } message: { - Text(NSLocalizedString("Existing DDI files will be removed before downloading fresh copies.", comment: "")) + Text(String(format: "Existing DDI files will be removed before downloading fresh copies.".localized)) } .overlay { if isImportingFile { importBusyOverlay } } } @@ -240,7 +240,7 @@ struct SettingsView: View { private var importBusyOverlay: some View { Color.black.opacity(0.35).ignoresSafeArea() VStack(spacing: 12) { - ProgressView(NSLocalizedString("Processing pairing file…", comment: "")) + ProgressView(String(format: "Processing pairing file…".localized)) VStack(spacing: 8) { GeometryReader { geometry in ZStack(alignment: .leading) { @@ -276,11 +276,11 @@ struct SettingsView: View { let processInfo = ProcessInfo.processInfo let txmLabel: String if processInfo.isTXMOverridden { - txmLabel = NSLocalizedString("TXM (Override)", comment: "") + txmLabel = String(format: "TXM (Override)".localized) } else { - txmLabel = processInfo.hasTXM ? NSLocalizedString("TXM", comment: "") : NSLocalizedString("Non TXM", comment: "") + txmLabel = processInfo.hasTXM ? String(format: "TXM".localized) : String(format: "Non TXM".localized) } - return String(format: NSLocalizedString("Version %@ • iOS %@ • %@", comment: ""), appVersion, UIDevice.current.systemVersion, txmLabel) + return String(format: "Version %@ • iOS %@ • %@", appVersion, UIDevice.current.systemVersion, txmLabel) } // MARK: - Business Logic @@ -299,7 +299,7 @@ struct SettingsView: View { await MainActor.run { isRedownloadingDDI = true ddiDownloadProgress = 0 - ddiStatusMessage = NSLocalizedString("Preparing download…", comment: "") + ddiStatusMessage = String(format: "Preparing download…".localized) ddiResultMessage = nil } do { diff --git a/StikJIT/Views/ToolsView.swift b/StikJIT/Views/ToolsView.swift index 8b2f548b..4be9d4b0 100644 --- a/StikJIT/Views/ToolsView.swift +++ b/StikJIT/Views/ToolsView.swift @@ -18,12 +18,12 @@ struct ToolsView: View { private var tools: [ToolItem] { [ - ToolItem(id: "scripts", title: NSLocalizedString("Scripts", comment: ""), detail: NSLocalizedString("Manage and run JS scripts", comment: ""), systemImage: "scroll", destination: AnyView(ScriptListView())), - ToolItem(id: "console", title: NSLocalizedString("Console", comment: ""), detail: NSLocalizedString("Live device logs", comment: ""), systemImage: "terminal", destination: AnyView(ConsoleLogsView())), - ToolItem(id: "deviceinfo", title: NSLocalizedString("Device Info", comment: ""), detail: NSLocalizedString("View detailed device metadata", comment: ""), systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), - ToolItem(id: "profiles", title: NSLocalizedString("App Expiry", comment: ""), detail: NSLocalizedString("Check app expiration dates", comment: ""), systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), - ToolItem(id: "processes", title: NSLocalizedString("Processes", comment: ""), detail: NSLocalizedString("Inspect running apps", comment: ""), systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), - ToolItem(id: "location", title: NSLocalizedString("Location Simulation", comment: ""), detail: NSLocalizedString("Simulate GPS location", comment: ""), systemImage: "location", destination: AnyView(LocationSimulationView())) + ToolItem(id: "scripts", title: String(format: "Scripts".localized), detail: String(format: "Manage and run JS scripts".localized), systemImage: "scroll", destination: AnyView(ScriptListView())), + ToolItem(id: "console", title: String(format: "Console".localized), detail: String(format: "Live device logs".localized), systemImage: "terminal", destination: AnyView(ConsoleLogsView())), + ToolItem(id: "deviceinfo", title: String(format: "Device Info".localized), detail: String(format: "View detailed device metadata".localized), systemImage: "iphone.and.arrow.forward", destination: AnyView(DeviceInfoView())), + ToolItem(id: "profiles", title: String(format: "App Expiry".localized), detail: String(format: "Check app expiration dates".localized), systemImage: "calendar.badge.clock", destination: AnyView(ProfileView())), + ToolItem(id: "processes", title: String(format: "Processes".localized), detail: String(format: "Inspect running apps".localized), systemImage: "rectangle.stack.person.crop", destination: AnyView(ProcessInspectorView())), + ToolItem(id: "location", title: String(format: "Location Simulation".localized), detail: String(format: "Simulate GPS location".localized), systemImage: "location", destination: AnyView(LocationSimulationView())) ] } @@ -45,7 +45,7 @@ struct ToolsView: View { } } } - .navigationTitle(NSLocalizedString("Tools", comment: "")) + .navigationTitle(String(format: "Tools".localized)) } } } From ebf70d89735ae3da2bf56549765e98a5cc1a3510 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Wed, 13 May 2026 22:49:41 +0800 Subject: [PATCH 13/14] Filled a missing localization part: favorites --- StikJIT/Assets.xcassets/Localizable.xcstrings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index aa16a69c..57ffb598 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -664,19 +664,19 @@ } } }, - "Favorites" : { + "Favorites (%d/4)" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Favorites" + "value" : "Favorites (%d/4)" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "收藏" + "value" : "收藏 (%d/4)" } } } From 52f5880374f4ac567193a96e8ed6fa7f4cdc2185 Mon Sep 17 00:00:00 2001 From: jlj1102 Date: Tue, 19 May 2026 16:33:12 +0800 Subject: [PATCH 14/14] Updated Translation --- StikJIT/Assets.xcassets/Localizable.xcstrings | 733 +++++++++++++++++- StikJIT/StikJITApp.swift | 18 +- StikJIT/Utilities/AppFeature.swift | 42 +- StikJIT/Views/MapSelectionView.swift | 2 +- StikJIT/Views/NewsView.swift | 8 +- StikJIT/Views/ProcessInspectorView.swift | 44 +- StikJIT/Views/ProfileView.swift | 6 +- StikJIT/Views/SettingsView.swift | 14 +- 8 files changed, 799 insertions(+), 68 deletions(-) diff --git a/StikJIT/Assets.xcassets/Localizable.xcstrings b/StikJIT/Assets.xcassets/Localizable.xcstrings index 57ffb598..dfae63ed 100644 --- a/StikJIT/Assets.xcassets/Localizable.xcstrings +++ b/StikJIT/Assets.xcassets/Localizable.xcstrings @@ -324,6 +324,23 @@ } } }, + "Another process action is already running.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Another process action is already running." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "另一个进程操作正在运行。" + } + } + } + }, "Cancel" : { "extractionState" : "manual", "localizations" : { @@ -800,6 +817,23 @@ } } }, + "Resume": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复" + } + } + } + }, "Launch" : { "extractionState" : "manual", "localizations" : { @@ -970,6 +1004,125 @@ } } }, + "Missing entitlements:": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Missing entitlements:" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缺失权限:" + } + } + } + }, + "Profile removed successfully": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profile removed successfully" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置删除成功" + } + } + } + }, + "Failed to Remove Profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Remove Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置删除失败" + } + } + } + }, + "Profile added successfully": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profile added successfully" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置添加成功" + } + } + } + }, + "Success": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Success" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "成功" + } + } + } + }, + "Failed to Add Profile": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to Add Profile" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置添加失败" + } + } + } + }, + "No matching processes.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No matching processes." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无匹配进程" + } + } + } + }, "Name" : { "extractionState" : "manual", "localizations" : { @@ -982,7 +1135,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "名字" + "value" : "地名" } } } @@ -1021,6 +1174,23 @@ } } }, + "Enter a name for this location.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter a name for this location." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "请输入此位置的名称。" + } + } + } + }, "No JIT Apps Found" : { "extractionState" : "manual", "localizations" : { @@ -2075,6 +2245,23 @@ } } }, + "Version %@": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Version %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本 %@" + } + } + } + }, "View detailed device metadata": { "extractionState" : "manual", "localizations" : { @@ -2941,6 +3128,550 @@ } } } + }, + "Read More": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Read More" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "阅读更多" + } + } + } + }, + "No News": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No News" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有新闻" + } + } + } + }, + "Could Not Load News": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could Not Load News" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法加载新闻" + } + } + } + }, + "News": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "News" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新闻" + } + } + } + }, + "Loading News": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading News" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在加载新闻" + } + } + } + }, + "Import Coordinates": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Import Coordinates" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入坐标" + } + } + } + }, + "Latest StikDebug updates": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Latest StikDebug updates" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "StikDebug 最近更新" + } + } + } + }, + "Configure StikDebug": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configure StikDebug" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置 StikDebug" + } + } + } + }, + "The pairing file is invalid or expired. Please select a new pairing file.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The pairing file is invalid or expired. Please select a new pairing file." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配对文件无效或过期。请选择新的配对文件。" + } + } + } + }, + "Select New File": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select New File" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择新文件" + } + } + } + }, + "Connection Error": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connection Error" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接错误" + } + } + } + }, + "%@\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@\n\n请确保 Wi‑Fi 和 LocalDevVPN 都已连接,并且设备可访问。" + } + } + } + }, + "DDI Mount Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DDI Mount Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开发者镜像挂载失败" + } + } + } + }, + "An Error has Occurred": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "An Error has Occurred" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发生错误" + } + } + } + }, + "Resuming Process": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resuming Process" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复进程中" + } + } + } + }, + "Pausing Process": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausing Process" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停进程中" + } + } + } + }, + "Terminating Process": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminating Process" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终止进程中" + } + } + } + }, + "Resume Timed Out": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume Timed Out" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复超时" + } + } + } + }, + "Pause Timed Out": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause Timed Out" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停超时" + } + } + } + }, + "Kill Timed Out": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kill Timed Out" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终止超时" + } + } + } + }, + "Resume Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法恢复进程" + } + } + } + }, + "Pause Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法暂停进程" + } + } + } + }, + "Kill Failed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kill Failed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法终止进程" + } + } + } + }, + "Process Resumed": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Process Resumed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进程已恢复" + } + } + } + }, + "Process Paused": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Process Paused" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进程已暂停" + } + } + } + }, + "Process Terminated": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Process Terminated" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进程已终止" + } + } + } + }, + "Sent SIGCONT (19) to PID %@.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sent SIGCONT (19) to PID %@." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已发送 SIGCONT (19) 给 PID %@。" + } + } + } + }, + "Sent SIGSTOP (17) to PID %@.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sent SIGSTOP (17) to PID %@." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已发送 SIGSTOP (17) 给 PID %@。" + } + } + } + }, + "PID %@ was terminated.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %@ was terminated." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %@ 已被终止。" + } + } + } + }, + "Could not confirm resume for PID %@. Try again.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not confirm resume for PID %@. Try again." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法确认 PID %@ 已恢复。请再试一次。" + } + } + } + }, + "Could not confirm pause for PID %@. Try again.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not confirm pause for PID %@. Try again." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法确认 PID %@ 已暂停。请再试一次。" + } + } + } + }, + "Could not confirm termination for PID %@. Try again.": { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not confirm termination for PID %@. Try again." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法确认 PID %@ 已终止。请再试一次。" + } + } + } } }, "version" : "1.1" diff --git a/StikJIT/StikJITApp.swift b/StikJIT/StikJITApp.swift index c3c880a6..18c4d1bd 100644 --- a/StikJIT/StikJITApp.swift +++ b/StikJIT/StikJITApp.swift @@ -169,8 +169,8 @@ struct HeartbeatApp: App { try await downloadFile(from: item.urlString, to: destinationURL) } catch { await MainActor.run { - showAlert(title: "An Error has Occurred", - message: "[Download DDI Error]: \(error.localizedDescription)", + showAlert(title: String(format: "An Error has Occurred".localized), + message: String(format: "[Download DDI Error]: %@".localized, error.localizedDescription), showOk: true) } break @@ -251,7 +251,7 @@ class MountingProgress: ObservableObject { DispatchQueue.main.async { if let mountError { - showAlert(title: "DDI Mount Failed", message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in + showAlert(title: String(format: "DDI Mount Failed".localized), message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in if shouldTryAgain { self.mount() } } } else { @@ -329,18 +329,18 @@ func startTunnelInBackground(showErrorUI: Bool = true) { } showAlert( - title: "Invalid Pairing File", - message: "The pairing file is invalid or expired. Please select a new pairing file.", + title: String(format: "Invalid Pairing File".localized), + message: String(format: "The pairing file is invalid or expired. Please select a new pairing file.".localized), showOk: true, showTryAgain: false, - primaryButtonText: "Select New File" + primaryButtonText: String(format: "Select New File".localized) ) { _ in NotificationCenter.default.post(name: NSNotification.Name("ShowPairingFilePicker"), object: nil) } } else { showAlert( - title: "Connection Error", - message: "\(error.localizedDescription)\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable.", + title: String(format: "Connection Error".localized), + message: String(format: "%@\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable.", error.localizedDescription), showOk: false, showTryAgain: true ) { shouldTryAgain in @@ -370,7 +370,7 @@ func checkDeviceConnection(callback: @escaping (Bool, String?) -> Void) { connection?.cancel() DispatchQueue.main.async { if timeoutWorkItem?.isCancelled == false { - let message = "[TIMEOUT] Could not reach the device at \(targetIP). Make sure it’s online and on the same network." + let message = String(format: "[TIMEOUT] Could not reach the device at \(targetIP). Make sure it’s online and on the same network.".localized) callback(false, message) } } diff --git a/StikJIT/Utilities/AppFeature.swift b/StikJIT/Utilities/AppFeature.swift index 4395861f..baa48dc3 100644 --- a/StikJIT/Utilities/AppFeature.swift +++ b/StikJIT/Utilities/AppFeature.swift @@ -24,57 +24,57 @@ enum AppFeature: String, CaseIterable, Identifiable { var title: String { switch self { case .home: - return "Apps" + return String(format: "Apps".localized) case .scripts: - return "Scripts" + return String(format: "Scripts".localized) case .tools: - return "Tools" + return String(format: "Tools".localized) case .news: - return "News" + return String(format: "News".localized) case .console: - return "Console" + return String(format: "Console".localized) case .deviceInfo: - return "Device Info" + return String(format: "Device Info".localized) case .profiles: - return "App Expiry" + return String(format: "App Expiry".localized) case .processes: - return "Processes" + return String(format: "Processes".localized) case .location: - return "Location" + return String(format: "Location".localized) case .settings: - return "Settings" + return String(format: "Settings".localized) } } var detail: String { switch self { case .home: - return "Manage installed apps" + return String(format: "Manage installed apps".localized) case .scripts: - return "Manage and run JS scripts" + return String(format: "Manage and run JS scripts".localized) case .tools: - return "Access additional tools" + return String(format: "Access additional tools".localized) case .news: - return "Latest StikDebug updates" + return String(format: "Latest StikDebug updates".localized) case .console: - return "Live device logs" + return String(format: "Live device logs".localized) case .deviceInfo: - return "View detailed device metadata" + return String(format: "View detailed device metadata".localized) case .profiles: - return "Check app expiration dates" + return String(format: "Check app expiration dates".localized) case .processes: - return "Inspect running apps" + return String(format: "Inspect running apps".localized) case .location: - return "Simulate GPS location" + return String(format: "Simulate GPS location".localized) case .settings: - return "Configure StikDebug" + return String(format: "Configure StikDebug".localized) } } var toolTitle: String { switch self { case .location: - return "Location Simulation" + return String(format: "Location Simulation".localized) default: return title } diff --git a/StikJIT/Views/MapSelectionView.swift b/StikJIT/Views/MapSelectionView.swift index 72bec5e3..f6f2e328 100644 --- a/StikJIT/Views/MapSelectionView.swift +++ b/StikJIT/Views/MapSelectionView.swift @@ -971,7 +971,7 @@ struct LocationSimulationView: View { Image(systemName: "square.and.arrow.down") } .disabled(isBusy || isRouteRunning || isImportingCoordinates) - .accessibilityLabel("Import Coordinates") + .accessibilityLabel(String(format: "Import Coordinates".localized)) } ToolbarItem(placement: .topBarTrailing) { TextField("Search location...", text: $searchText) diff --git a/StikJIT/Views/NewsView.swift b/StikJIT/Views/NewsView.swift index 06330e19..3afd4049 100644 --- a/StikJIT/Views/NewsView.swift +++ b/StikJIT/Views/NewsView.swift @@ -40,7 +40,7 @@ struct NewsView: View { Button { Task { await loadNews() } } label: { - Label("Refresh", systemImage: "arrow.clockwise") + Label(String(format: "Refresh".localized), systemImage: "arrow.clockwise") } .disabled(isLoading) } @@ -67,7 +67,7 @@ struct NewsView: View { .frame(maxWidth: .infinity, minHeight: 220) } else if let errorMessage { ContentUnavailableView { - Label("Could Not Load News", systemImage: "exclamationmark.triangle") + Label(String(format: "Could Not Load News".localized), systemImage: "exclamationmark.triangle") } description: { Text(errorMessage) } actions: { @@ -77,7 +77,7 @@ struct NewsView: View { } .frame(minHeight: 280) } else { - ContentUnavailableView("No News", systemImage: "newspaper") + ContentUnavailableView(String(format: "No News".localized), systemImage: "newspaper") .frame(minHeight: 280) } } @@ -271,7 +271,7 @@ private struct NewsCard: View { if let url = item.url { Link(destination: url) { - Label("Read More", systemImage: "arrow.up.right") + Label(String(format: "Read More".localized), systemImage: "arrow.up.right") .font(.callout.weight(.semibold)) } .buttonStyle(.bordered) diff --git a/StikJIT/Views/ProcessInspectorView.swift b/StikJIT/Views/ProcessInspectorView.swift index 8b386888..62d176ac 100644 --- a/StikJIT/Views/ProcessInspectorView.swift +++ b/StikJIT/Views/ProcessInspectorView.swift @@ -122,11 +122,11 @@ enum ProcessControlAction: String { var buttonLabel: String { switch self { case .resume: - return "Resume" + return String(format: "Resume".localized) case .pause: - return "Pause" + return String(format: "Pause".localized) case .kill: - return "Kill" + return String(format: "Kill".localized) } } @@ -155,66 +155,66 @@ enum ProcessControlAction: String { var progressTitle: String { switch self { case .resume: - return "Resuming Process" + return String(format: "Resuming Process".localized) case .pause: - return "Pausing Process" + return String(format: "Pausing Process".localized) case .kill: - return "Terminating Process" + return String(format: "Terminating Process".localized) } } var timeoutTitle: String { switch self { case .resume: - return "Resume Timed Out" + return String(format: "Resume Timed Out".localized) case .pause: - return "Pause Timed Out" + return String(format: "Pause Timed Out".localized) case .kill: - return "Kill Timed Out" + return String(format: "Kill Timed Out".localized) } } var failureTitle: String { switch self { case .resume: - return "Resume Failed" + return String(format: "Resume Failed".localized) case .pause: - return "Pause Failed" + return String(format: "Pause Failed".localized) case .kill: - return "Kill Failed" + return String(format: "Kill Failed".localized) } } var successTitle: String { switch self { case .resume: - return "Process Resumed" + return String(format: "Process Resumed".localized) case .pause: - return "Process Paused" + return String(format: "Process Paused".localized) case .kill: - return "Process Terminated" + return String(format: "Process Terminated".localized) } } func successMessage(for pid: Int) -> String { switch self { case .resume: - return "Sent SIGCONT (19) to PID \(pid)." + return String(format: "Sent SIGCONT (19) to PID %@.".localized, pid) case .pause: - return "Sent SIGSTOP (17) to PID \(pid)." + return String(format: "Sent SIGSTOP (17) to PID %@.".localized, pid) case .kill: - return "PID \(pid) was terminated." + return String(format: "PID %@ was terminated.".localized, pid) } } func timeoutMessage(for pid: Int) -> String { switch self { case .resume: - return "Could not confirm resume for PID \(pid). Try again." + return String(format: "Could not confirm resume for PID %@. Try again.".localized, pid) case .pause: - return "Could not confirm pause for PID \(pid). Try again." + return String(format: "Could not confirm pause for PID %@. Try again.".localized, pid) case .kill: - return "Could not confirm termination for PID \(pid). Try again." + return String(format: "Could not confirm termination for PID %@. Try again.".localized, pid) } } } @@ -284,7 +284,7 @@ private struct ProcessRow: View { onKillTap(process) } label: { if isConfirming { - Label("Confirm", systemImage: "checkmark.circle.fill") + Label(String(format: "Confirm".localized), systemImage: "checkmark.circle.fill") .labelStyle(.iconOnly) .font(.title3) } else { diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 0af20d00..51d57003 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -140,7 +140,7 @@ struct ProfileView: View { Section { HStack { Spacer() - ProgressView("Loading...") + ProgressView(String(format: "Loading...".localized)) Spacer() } } @@ -238,13 +238,13 @@ struct ProfileView: View { Button { isImporterPresented = true } label: { - Label("Add", systemImage: "plus") + Label(String(format: "Add".localized), systemImage: "plus") } Button { Task { await loadData(force: true) } } label: { - Label("Reload", systemImage: "arrow.clockwise") + Label(String(format: "Reload".localized), systemImage: "arrow.clockwise") } } diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index eb5779e2..7c56abd2 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -54,7 +54,7 @@ struct SettingsView: View { Section { Link(destination: SettingsLinks.githubStars) { - Label("Star on GitHub", systemImage: "star") + Label(String(format: "Star on GitHub".localized), systemImage: "star") } } @@ -62,7 +62,7 @@ struct SettingsView: View { Button { isShowingPairingFilePicker = true } label: { - Label("Import Pairing File", systemImage: "doc.badge.plus") + Label(String(format: "Import Pairing File".localized), systemImage: "doc.badge.plus") } .disabled(isImportingFile) @@ -70,7 +70,7 @@ struct SettingsView: View { HStack(spacing: 10) { ProgressView() .controlSize(.small) - Text("Importing pairing file…") + Text(String(format: "Importing pairing file…".localized)) .font(.caption) .foregroundStyle(.secondary) } @@ -149,18 +149,18 @@ struct SettingsView: View { Section("Help") { Link(destination: SettingsLinks.pairingFileGuide) { - Label("Pairing File Guide", systemImage: "questionmark.circle") + Label(String(format: "Pairing File Guide".localized), systemImage: "questionmark.circle") } Link(destination: SettingsLinks.localDevVPN) { - Label("Download LocalDevVPN", systemImage: "arrow.down.circle") + Label(String(format: "Download LocalDevVPN".localized), systemImage: "arrow.down.circle") } Link(destination: SettingsLinks.discord) { - Label("Discord Support", systemImage: "bubble.left.and.bubble.right") + Label(String(format: "Discord Support".localized), systemImage: "bubble.left.and.bubble.right") } } Section { - Text(versionFooter) + Text(String(format: "Version %@".localized, versionFooter)) .font(.footnote).foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .center) .listRowBackground(Color.clear)