From ca2d5cda53690d031ad155bcff75935c9337a96f Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 02:27:33 +0100 Subject: [PATCH 1/6] fix: clarified accessible control context - Added target versions to install and cancel controls. - Preserved a meaningful filter name in native toolbars. - Kept visible labels and action behaviour unchanged. --- Xcodes/Backend/XcodeCommands.swift | 4 ++++ Xcodes/Frontend/XcodeList/MainToolbar.swift | 1 + 2 files changed, 5 insertions(+) diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index 9198782c..38df68eb 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -45,6 +45,7 @@ struct InstallButton: View { Text("Install") .help("InstallDescription") } + .accessibilityValue(xcode?.description ?? "") } private func install() { @@ -63,6 +64,7 @@ struct CancelInstallButton: View { } .help(localizeString("StopInstallation")) .buttonStyle(.plain) + .accessibilityValue(xcode?.description ?? "") } private func cancelInstall() { @@ -82,6 +84,7 @@ struct CancelRuntimeInstallButton: View { .labelStyle(.iconOnly) .help(localizeString("StopInstallation")) .buttonStyle(.plain) + .accessibilityValue(runtime?.visibleIdentifier ?? "") } private func cancelInstall() { @@ -246,6 +249,7 @@ struct DownloadRuntimeButton: View { Text("Install") .help("Install") } + .accessibilityValue(runtime?.visibleIdentifier ?? "") } private func install() { diff --git a/Xcodes/Frontend/XcodeList/MainToolbar.swift b/Xcodes/Frontend/XcodeList/MainToolbar.swift index c04156b0..0662fffb 100644 --- a/Xcodes/Frontend/XcodeList/MainToolbar.swift +++ b/Xcodes/Frontend/XcodeList/MainToolbar.swift @@ -71,6 +71,7 @@ struct MainToolbarModifier: ViewModifier { } .pickerStyle(.inline) .symbolVariant(isFiltering ? .fill : .none) + .accessibilityLabel(Text("Filter")) } } From 77dffae7f21a6cbe1a3b06f067c6de1389d677bb Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 02:37:27 +0100 Subject: [PATCH 2/6] fix: enlarged targets and clarified deletion - Enlarged undersized icon controls to the macOS minimum target size. - Explained runtime removal consequences and recovery across supported locales. - Prepared version 4.1.3 (51) for the accessibility follow-up. --- Xcodes.xcodeproj/project.pbxproj | 12 +- .../Preferences/PlatformsListView.swift | 2 + Xcodes/Frontend/XcodeList/XcodeListView.swift | 4 + .../Frontend/XcodeList/XcodeListViewRow.swift | 4 + Xcodes/Resources/Localizable.xcstrings | 131 ++++++++++++++++++ Xcodes/XcodesApp.swift | 1 + 6 files changed, 148 insertions(+), 6 deletions(-) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 3a41f83a..f95d8a16 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -1094,7 +1094,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_TEAM = K2648T24P4; ENABLE_HARDENED_RUNTIME = NO; @@ -1106,7 +1106,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.6; - MARKETING_VERSION = 4.1.2; + MARKETING_VERSION = 4.1.3; PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes; PRODUCT_NAME = Xcodes; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1347,7 +1347,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_TEAM = K2648T24P4; ENABLE_HARDENED_RUNTIME = YES; @@ -1359,7 +1359,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.6; - MARKETING_VERSION = 4.1.2; + MARKETING_VERSION = 4.1.3; PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes; PRODUCT_NAME = Xcodes; SWIFT_VERSION = 6.0; @@ -1376,7 +1376,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_TEAM = K2648T24P4; ENABLE_HARDENED_RUNTIME = YES; @@ -1388,7 +1388,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.6; - MARKETING_VERSION = 4.1.2; + MARKETING_VERSION = 4.1.3; PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes; PRODUCT_NAME = Xcodes; SWIFT_VERSION = 6.0; diff --git a/Xcodes/Frontend/Preferences/PlatformsListView.swift b/Xcodes/Frontend/Preferences/PlatformsListView.swift index 773ed20c..f91a7b48 100644 --- a/Xcodes/Frontend/Preferences/PlatformsListView.swift +++ b/Xcodes/Frontend/Preferences/PlatformsListView.swift @@ -56,6 +56,8 @@ struct PlatformsListView: View { deleteRuntime(runtime: installedRuntime) } label: { Label("Alert.DeletePlatform.PrimaryButton", systemImage: "trash") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .accessibilityValue(runtime.name) diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 0cebb150..23f7fe59 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -302,6 +302,8 @@ private struct XcodeVersionGroupRow: View { Button(action: { appState.select(xcode: latestSelectionTarget) }) { Image(systemName: "arrow.up.circle.fill") .foregroundColor(.yellow) + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .accessibilityLabel(Text("MakeActiveVersionDescription")) .buttonStyle(PlainButtonStyle()) @@ -329,6 +331,8 @@ private struct XcodeVersionGroupRow: View { Button(action: { appState.select(xcode: latestSelectionTarget) }) { Image(systemName: "checkmark.circle") .foregroundColor(.secondary) + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .accessibilityLabel(Text("MakeActiveVersionDescription")) .buttonStyle(PlainButtonStyle()) diff --git a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift index 1798e0b8..c98ec038 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift @@ -121,6 +121,8 @@ struct XcodeListViewRow: View { Button(action: { appState.select(xcode: latestReleaseForSelectedPrerelease) }) { Label("MakeActive", systemImage: "arrow.up.circle.fill") .foregroundColor(.yellow) + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) @@ -142,6 +144,8 @@ struct XcodeListViewRow: View { Button(action: { appState.select(xcode: xcode) }) { Label("MakeActive", systemImage: "checkmark.circle") .foregroundColor(.secondary) + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index 315286f5..08ba4620 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -3753,6 +3753,137 @@ } } }, + "Alert.DeletePlatform.Message" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "سيؤدي هذا إلى حذف بيئة تشغيل المحاكي نهائيًا. ستتوقف أي محاكيات قيد التشغيل تستخدمها. أعد تثبيت بيئة التشغيل لاستخدام تلك المحاكيات مجددًا." + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Això suprimeix permanentment l’entorn d’execució del simulador. Els simuladors en execució que l’utilitzin s’aturaran. Torna a instal·lar l’entorn d’execució per tornar a utilitzar aquests simuladors." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die Simulator-Laufzeitumgebung wird dauerhaft gelöscht. Alle laufenden Simulatoren, die sie verwenden, werden heruntergefahren. Installiere die Laufzeitumgebung erneut, um diese Simulatoren wieder zu verwenden." + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Αυτό διαγράφει οριστικά το περιβάλλον εκτέλεσης του προσομοιωτή. Όλοι οι ενεργοί προσομοιωτές που το χρησιμοποιούν θα τερματιστούν. Εγκαταστήστε ξανά το περιβάλλον εκτέλεσης για να χρησιμοποιήσετε ξανά αυτούς τους προσομοιωτές." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This permanently deletes the simulator runtime. Any running simulators that use it will shut down. Reinstall the runtime to use those simulators again." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto elimina permanentemente el entorno de ejecución del simulador. Los simuladores en ejecución que lo utilicen se apagarán. Vuelve a instalar el entorno de ejecución para usar esos simuladores de nuevo." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tämä poistaa simulaattorin ajonaikaisen ympäristön pysyvästi. Kaikki sitä käyttävät käynnissä olevat simulaattorit sammutetaan. Asenna ajonaikainen ympäristö uudelleen, jotta voit käyttää näitä simulaattoreita uudelleen." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cette action supprime définitivement l’environnement d’exécution du simulateur. Les simulateurs en cours d’exécution qui l’utilisent s’arrêteront. Réinstallez l’environnement d’exécution pour utiliser à nouveau ces simulateurs." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "इससे सिम्युलेटर का रनटाइम स्थायी रूप से मिट जाएगा। इसका उपयोग कर रहे सभी चालू सिम्युलेटर बंद हो जाएँगे। उन सिम्युलेटरों का दोबारा उपयोग करने के लिए रनटाइम फिर से इंस्टॉल करें।" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Questa operazione elimina definitivamente l’ambiente di esecuzione del simulatore. Tutti i simulatori in esecuzione che lo utilizzano verranno arrestati. Reinstalla l’ambiente di esecuzione per usare nuovamente quei simulatori." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "シミュレータのランタイムが完全に削除されます。このランタイムを使用して実行中のシミュレータはすべてシャットダウンされます。これらのシミュレータを再び使用するには、ランタイムを再インストールしてください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "시뮬레이터 런타임이 영구적으로 삭제됩니다. 이 런타임을 사용하는 실행 중인 시뮬레이터가 모두 종료됩니다. 해당 시뮬레이터를 다시 사용하려면 런타임을 다시 설치하세요." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiermee wordt de simulatorruntime permanent verwijderd. Alle actieve simulators die deze gebruiken, worden afgesloten. Installeer de runtime opnieuw om die simulators weer te gebruiken." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spowoduje to trwałe usunięcie środowiska uruchomieniowego symulatora. Wszystkie działające symulatory, które z niego korzystają, zostaną wyłączone. Zainstaluj ponownie środowisko uruchomieniowe, aby znów korzystać z tych symulatorów." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso exclui permanentemente o ambiente de execução do simulador. Todos os simuladores em execução que o utilizam serão desligados. Reinstale o ambiente de execução para usar esses simuladores novamente." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Среда выполнения симулятора будет удалена безвозвратно. Все запущенные симуляторы, которые её используют, будут выключены. Установите среду выполнения заново, чтобы снова использовать эти симуляторы." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "การดำเนินการนี้จะลบรันไทม์ของตัวจำลองอย่างถาวร ตัวจำลองที่กำลังทำงานและใช้รันไทม์นี้จะปิดลง ติดตั้งรันไทม์อีกครั้งเพื่อใช้ตัวจำลองเหล่านั้นอีกครั้ง" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu işlem simülatör çalışma zamanını kalıcı olarak siler. Bu çalışma zamanını kullanan tüm açık simülatörler kapatılır. Bu simülatörleri tekrar kullanmak için çalışma zamanını yeniden yükleyin." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Середовище виконання симулятора буде видалено назавжди. Усі запущені симулятори, які його використовують, буде вимкнено. Установіть середовище виконання знову, щоб знову використовувати ці симулятори." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将永久删除模拟器运行时。所有正在使用此运行时的模拟器都将关闭。请重新安装运行时,以便再次使用这些模拟器。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "這會永久刪除模擬器執行階段。所有正在使用此執行階段的模擬器都會關閉。請重新安裝執行階段,以便再次使用這些模擬器。" + } + } + } + }, "Alert.DeletePlatform.PrimaryButton" : { "extractionState" : "manual", "localizations" : { diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index b19534e6..f6318563 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -91,6 +91,7 @@ struct XcodesApp: App { case let .deletePlatform(runtime): return Alert( title: Text(String(format: localizeString("Alert.DeletePlatform.Title"), runtime.runtime.name)), + message: Text("Alert.DeletePlatform.Message"), primaryButton: .destructive( Text("Alert.DeletePlatform.PrimaryButton"), action: { From 2b64d123d6ac3c3a53fc4e34099cf34846f1add5 Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 02:46:17 +0100 Subject: [PATCH 3/6] test: repaired release identity validation - Aligned expected release identity with version 4.1.3 (51). - Restored clean fixtures before checking stale versions. - Replaced a stale no-op mutation with an asserted version-independent mutation. --- Scripts/check_fork_identity.sh | 4 ++-- Scripts/test_fork_identity_guard.sh | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Scripts/check_fork_identity.sh b/Scripts/check_fork_identity.sh index d3f631d0..aef6d40d 100755 --- a/Scripts/check_fork_identity.sh +++ b/Scripts/check_fork_identity.sh @@ -43,8 +43,8 @@ readonly app_id="dev.jacobcx.Xcodes" readonly tests_id="dev.jacobcx.Xcodes.Tests" readonly helper_id="dev.jacobcx.Xcodes.Helper" readonly team_id="K2648T24P4" -readonly marketing_version="4.1.2" -readonly build_number="50" +readonly marketing_version="4.1.3" +readonly build_number="51" readonly app_copyright="Fork contributions © 2026 JacobCXDev. Upstream contributors retain their copyrights." # shellcheck disable=SC2016 # Xcode expands this build-setting literal, not the shell. readonly app_requirement='identifier "dev.jacobcx.Xcodes" and info [CFBundleShortVersionString] >= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)"' diff --git a/Scripts/test_fork_identity_guard.sh b/Scripts/test_fork_identity_guard.sh index b583eebf..8862a4e5 100755 --- a/Scripts/test_fork_identity_guard.sh +++ b/Scripts/test_fork_identity_guard.sh @@ -108,11 +108,15 @@ if "$fixture_root/Scripts/check_fork_identity.sh" >/dev/null 2>&1; then exit 1 fi +cp "$repo_root/Xcodes/Resources/Info.plist" "$fixture_root/Xcodes/Resources/Info.plist" +cp "$repo_root/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift" "$fixture_root/Xcodes/Frontend/Preferences/" +"$fixture_root/Scripts/check_fork_identity.sh" >/dev/null + cp \ "$repo_root/Xcodes.xcodeproj/project.pbxproj" \ "$fixture_root/Xcodes.xcodeproj/project.pbxproj" perl -0pi -e \ - 's/MARKETING_VERSION = 4\.0\.5;/MARKETING_VERSION = 4.0.4;/g' \ + 's/MARKETING_VERSION = [^;]+;/MARKETING_VERSION = 0.0.0;/g or die "No marketing version found to mutate\n"' \ "$fixture_root/Xcodes.xcodeproj/project.pbxproj" if "$fixture_root/Scripts/check_fork_identity.sh" >/dev/null 2>&1; then From 13d6b4afb923fa21d2069379e8fecd31f551560f Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 03:02:27 +0100 Subject: [PATCH 4/6] fix: restored accessible input and copy - Exposed native verification-code input and preserved decorative digit hiding. - Enlarged small action targets without changing their behaviour. - Filled missing translations and corrected mixed-direction path copy. - Tightened localisation checks and added accessibility regression coverage. --- Scripts/check_localizations.rb | 27 +- Scripts/test_localization_contract.sh | 4 +- Xcodes/Backend/XcodeCommands.swift | 4 + .../InfoPane/InstalledStateButtons.swift | 2 + Xcodes/Frontend/InfoPane/PlatformsView.swift | 2 + .../Preferences/AdvancedPreferencePane.swift | 4 + Xcodes/Frontend/SignIn/PinCodeTextView.swift | 1 + .../XcodeList/InstallationStepRowView.swift | 2 + Xcodes/Frontend/XcodeList/XcodeListView.swift | 3 +- Xcodes/Resources/Localizable.xcstrings | 364 +++++++++++++++++- XcodesTests/PinCodeInputTests.swift | 3 + 11 files changed, 381 insertions(+), 35 deletions(-) diff --git a/Scripts/check_localizations.rb b/Scripts/check_localizations.rb index 4220d820..4c213fc2 100755 --- a/Scripts/check_localizations.rb +++ b/Scripts/check_localizations.rb @@ -5,25 +5,6 @@ catalog_path = ARGV.fetch(0, File.expand_path("../Xcodes/Resources/Localizable.xcstrings", __dir__)) catalog = JSON.parse(File.read(catalog_path)) languages = %w[ar ca de el es fi fr hi it ja ko nl pl pt-BR ru th tr uk zh-Hans zh-Hant].freeze -established_languages = languages - %w[ar th] -baseline_keys = %w[AutomaticallyCreateBetaSymbolicLink AutomaticallyCreateBetaSymbolicLinkDescription].freeze -arabic_thai_baseline_keys = [ - "An error occurred", - "Architecture", - "AutomaticallyCreateBetaSymbolicLink", - "AutomaticallyCreateBetaSymbolicLinkDescription", - "Category", - "Dismiss", - "FilterArchitecturesDescription", - "Installed Only", - "Open Browser", - "Paste redirected URL", - "Signing out...", -].freeze -allowed_missing = ( - baseline_keys.product(established_languages) + - arabic_thai_baseline_keys.product(%w[ar th]) -).map { |key, language| "#{key}:#{language}" }.sort.freeze translated = lambda do |localization| if localization.key?("stringUnit") @@ -118,11 +99,9 @@ "GroupXcodeVersionsInList:#{language} expected #{expected_value.inspect}, got #{actual_value.inspect}" unless actual_value == expected_value end -unexpected = missing - allowed_missing -resolved = allowed_missing - missing errors = [] -errors << "New missing or unreviewed translations:\n #{unexpected.join("\n ")}" unless unexpected.empty? -errors << "Localization baseline is stale; remove resolved entries:\n #{resolved.join("\n ")}" unless resolved.empty? +errors << "Supported localization languages do not match the grouping contract" unless languages.sort == (required_grouping_values.keys - ["en"]).sort +errors << "Missing or unreviewed translations:\n #{missing.join("\n ")}" unless missing.empty? errors << "Legacy Apple ID terminology remains:\n #{legacy_terminology.join("\n ")}" unless legacy_terminology.empty? errors << "Legacy localization keys remain:\n #{forbidden_keys.join("\n ")}" unless forbidden_keys.empty? errors << "Required localization keys are missing:\n #{required_keys.join("\n ")}" unless required_keys.empty? @@ -133,4 +112,4 @@ exit 1 end -puts "Localization contract passed with #{allowed_missing.length} explicitly tracked gaps" +puts "Localization contract passed with no missing translations" diff --git a/Scripts/test_localization_contract.sh b/Scripts/test_localization_contract.sh index 69b66398..ee640277 100755 --- a/Scripts/test_localization_contract.sh +++ b/Scripts/test_localization_contract.sh @@ -41,8 +41,8 @@ expect_failure new_arabic_gap \ 'path = ARGV.fetch(0); data = JSON.parse(File.read(path)); data["strings"]["AutomaticallyCreateSymbolicLink"]["localizations"].delete("ar"); File.write(path, JSON.pretty_generate(data))' expect_failure new_thai_gap \ 'path = ARGV.fetch(0); data = JSON.parse(File.read(path)); data["strings"]["AutomaticallyCreateSymbolicLink"]["localizations"].delete("th"); File.write(path, JSON.pretty_generate(data))' -expect_failure stale_baseline \ - 'path = ARGV.fetch(0); data = JSON.parse(File.read(path)); data["strings"]["AutomaticallyCreateBetaSymbolicLink"]["localizations"]["de"] = { "stringUnit" => { "state" => "translated", "value" => "Beta-Link" } }; File.write(path, JSON.pretty_generate(data))' +expect_failure removed_beta_translation \ + 'path = ARGV.fetch(0); data = JSON.parse(File.read(path)); data["strings"]["AutomaticallyCreateBetaSymbolicLink"]["localizations"].delete("de"); File.write(path, JSON.pretty_generate(data))' expect_failure legacy_apple_id_copy \ 'path = ARGV.fetch(0); data = JSON.parse(File.read(path)); data["strings"]["AccessGranted"]["localizations"]["en"]["stringUnit"]["value"] = "Signed in with your Apple ID."; File.write(path, JSON.pretty_generate(data))' expect_failure shifted_grouping_translation \ diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index 38df68eb..cfe8fccd 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -61,6 +61,8 @@ struct CancelInstallButton: View { var body: some View { Button(action: cancelInstall) { Label("Cancel", systemImage: "xmark") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .help(localizeString("StopInstallation")) .buttonStyle(.plain) @@ -80,6 +82,8 @@ struct CancelRuntimeInstallButton: View { var body: some View { Button(action: cancelInstall) { Label("StopInstallation", systemImage: "xmark.circle.fill") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .help(localizeString("StopInstallation")) diff --git a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift index 541e2fcf..d225a79e 100644 --- a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift @@ -22,6 +22,8 @@ struct InstalledStateButtons: View { Text(xcode.installedPath?.string ?? "") Button(action: { appState.reveal(xcode.installedPath) }) { Label("RevealInFinder", systemImage: "arrow.right.circle.fill") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) diff --git a/Xcodes/Frontend/InfoPane/PlatformsView.swift b/Xcodes/Frontend/InfoPane/PlatformsView.swift index 40aa124b..a0fd1415 100644 --- a/Xcodes/Frontend/InfoPane/PlatformsView.swift +++ b/Xcodes/Frontend/InfoPane/PlatformsView.swift @@ -129,6 +129,8 @@ struct PlatformsView: View { if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) { Button(action: { appState.reveal(path: path.string) }) { Label("RevealInFinder", systemImage: "arrow.right.circle.fill") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) diff --git a/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift b/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift index abbc91c9..75ed4b52 100644 --- a/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift +++ b/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift @@ -16,6 +16,8 @@ struct AdvancedPreferencePane: View { .lineLimit(2) Button(action: { appState.reveal(path: appState.installPath) }) { Label("RevealInFinder", systemImage: "arrow.right.circle.fill") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) @@ -54,6 +56,8 @@ struct AdvancedPreferencePane: View { .lineLimit(2) Button(action: { appState.reveal(path: appState.localPath) }) { Label("RevealInFinder", systemImage: "arrow.right.circle.fill") + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .labelStyle(.iconOnly) .buttonStyle(PlainButtonStyle()) diff --git a/Xcodes/Frontend/SignIn/PinCodeTextView.swift b/Xcodes/Frontend/SignIn/PinCodeTextView.swift index b3cc287b..37af3d86 100644 --- a/Xcodes/Frontend/SignIn/PinCodeTextView.swift +++ b/Xcodes/Frontend/SignIn/PinCodeTextView.swift @@ -132,6 +132,7 @@ final class PinCodeTextView: NSControl, NSTextFieldDelegate { inputField.delegate = self inputField.setAccessibilityLabel(accessibilityLabel) addSubview(inputField) + setAccessibilityChildren([inputField.cell].compactMap { $0 }) NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: topAnchor), diff --git a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift index 1d605fa0..a6c0dcb3 100644 --- a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift +++ b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift @@ -29,6 +29,8 @@ struct InstallationStepRowView: View { Button(action: cancel) { Label("Cancel", systemImage: "xmark.circle.fill") .labelStyle(IconOnlyLabelStyle()) + .frame(minWidth: 20, minHeight: 20) + .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) .foregroundColor(highlighted ? .white : .secondary) diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 23f7fe59..f22d3573 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -423,7 +423,8 @@ struct PlatformsPocket: View { openWindow(id: "platforms") } label: { Label("PlatformsDescription", systemImage: "square.3.layers.3d") - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, minHeight: 20, alignment: .leading) + .contentShape(Rectangle()) } .buttonStyle(.borderless) .controlSize(.regular) diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index 08ba4620..b6229ed5 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -6894,6 +6894,18 @@ }, "An error occurred" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حدث خطأ" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เกิดข้อผิดพลาด" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -7390,6 +7402,18 @@ }, "Architecture" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "البنية" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "สถาปัตยกรรม" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -7893,6 +7917,126 @@ }, "AutomaticallyCreateBetaSymbolicLink" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إنشاء رابط رمزي إلى Xcode-Beta.app تلقائيًا" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea automàticament un enllaç simbòlic a Xcode-Beta.app" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Symbolischen Link zu Xcode-Beta.app automatisch erstellen" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Αυτόματη δημιουργία συμβολικού συνδέσμου προς το Xcode-Beta.app" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crear automáticamente un enlace simbólico a Xcode-Beta.app" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Luo symbolinen linkki Xcode-Beta.appiin automaattisesti" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer automatiquement un lien symbolique vers Xcode-Beta.app" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode-Beta.app के लिए सिम्बॉलिक लिंक अपने आप बनाएँ" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea automaticamente un link simbolico a Xcode-Beta.app" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode-Beta.appへのシンボリックリンクを自動作成" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode-Beta.app 심볼릭 링크 자동 생성" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch een symbolische koppeling naar Xcode-Beta.app maken" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatycznie twórz dowiązanie symboliczne do Xcode-Beta.app" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Criar automaticamente um link simbólico para Xcode-Beta.app" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматически создавать символическую ссылку Xcode-Beta.app" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "สร้างลิงก์สัญลักษณ์ไปยัง Xcode-Beta.app โดยอัตโนมัติ" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode-Beta.app için otomatik sembolik bağlantı oluştur" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматично створювати символічне посилання Xcode-Beta.app" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动创建指向 Xcode-Beta.app 的符号链接" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動建立指向 Xcode-Beta.app 的符號連結" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -7903,6 +8047,126 @@ }, "AutomaticallyCreateBetaSymbolicLinkDescription" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "عند تعيين إصدار تجريبي من Xcode كإصدار نشط، يتم إنشاء Xcode-Beta.app أو تحديثه في دليل التثبيت ليرتبط بذلك الإصدار." + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quan activis una versió preliminar d’Xcode, crea o actualitza Xcode-Beta.app al directori d’instal·lació perquè enllaci amb aquesta versió." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wenn du eine Vorabversion von Xcode aktivierst, wird Xcode-Beta.app im Installationsverzeichnis erstellt oder aktualisiert und mit dieser Version verknüpft." + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Όταν ενεργοποιείτε μια προέκδοση του Xcode, δημιουργείται ή ενημερώνεται το Xcode-Beta.app στον φάκελο εγκατάστασης ώστε να παραπέμπει σε αυτήν την έκδοση." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Al activar una versión preliminar de Xcode, se crea o actualiza Xcode-Beta.app en el directorio de instalación para enlazar con esa versión." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kun aktivoit Xcoden ennakkoversion, asennushakemistoon luodaan tai päivitetään Xcode-Beta.app-linkki, joka osoittaa kyseiseen versioon." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lorsque vous activez une préversion de Xcode, Xcode-Beta.app est créé ou mis à jour dans le répertoire d’installation pour pointer vers cette version." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode के किसी पूर्व-रिलीज़ संस्करण को सक्रिय करने पर, इंस्टॉलेशन फ़ोल्डर में Xcode-Beta.app बनाएँ या अपडेट करें ताकि वह उस संस्करण से जुड़ सके।" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quando attivi una versione preliminare di Xcode, crea o aggiorna Xcode-Beta.app nella cartella di installazione per collegarlo a quella versione." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcodeのプレリリース版をアクティブにすると、インストール先にXcode-Beta.appを作成または更新して、そのバージョンへのリンクにします。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xcode 사전 출시 버전을 활성화하면 설치 디렉터리에 해당 버전을 가리키는 Xcode-Beta.app 링크를 생성하거나 업데이트합니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Als je een voorlopige Xcode-versie actief maakt, wordt Xcode-Beta.app in de installatiemap aangemaakt of bijgewerkt om naar die versie te verwijzen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Po aktywowaniu przedpremierowej wersji Xcode w katalogu instalacyjnym zostanie utworzone lub zaktualizowane dowiązanie Xcode-Beta.app wskazujące tę wersję." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ao ativar uma versão de pré-lançamento do Xcode, crie ou atualize Xcode-Beta.app no diretório de instalação para apontar para essa versão." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "При активации предварительной версии Xcode в каталоге установки создаётся или обновляется ссылка Xcode-Beta.app на эту версию." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เมื่อกำหนดให้ Xcode รุ่นก่อนเผยแพร่เป็นรุ่นที่ใช้งานอยู่ ให้สร้างหรืออัปเดต Xcode-Beta.app ในไดเรกทอรีติดตั้งเพื่อเชื่อมโยงไปยังรุ่นนั้น" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir Xcode ön sürümünü etkinleştirdiğinizde, o sürüme bağlantı vermek için yükleme klasöründe Xcode-Beta.app oluşturulur veya güncellenir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Коли ви активуєте попередню версію Xcode, у каталозі встановлення створюється або оновлюється посилання Xcode-Beta.app на цю версію." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将 Xcode 预发布版本设为活跃版本时,在安装目录中创建或更新 Xcode-Beta.app,使其链接到该版本。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "將 Xcode 預先發行版本設為使用中版本時,在安裝目錄中建立或更新 Xcode-Beta.app,使其連結至該版本。" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -8428,6 +8692,18 @@ }, "Category" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الفئة" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "หมวดหมู่" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -10464,6 +10740,18 @@ }, "Dismiss" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إغلاق" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปิด" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -12382,6 +12670,18 @@ }, "FilterArchitecturesDescription" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تصفية إصدارات Xcode حسب البنية" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "กรองเวอร์ชัน Xcode ตามสถาปัตยกรรม" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -17057,6 +17357,18 @@ }, "Installed Only" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المثبتة فقط" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ที่ติดตั้งแล้วเท่านั้น" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -17302,7 +17614,7 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يقوم Xcodes بالبحث والتثبيت في دليل واحد. بشكل افتراضي (والموصى به) هو الاحتفاظ بهذا /Applications.online. قد تؤدي أي تغييرات في مكان تخزين Xcode إلى توقف التطبيقات/الخدمات الأخرى عن العمل." + "value" : "الدليل الافتراضي والموصى به هو \u2066/Applications\u2069. يبحث Xcodes عن التطبيقات ويثبّتها في دليل واحد. قد يؤدي تغيير مكان تخزين Xcode إلى توقف التطبيقات والخدمات الأخرى عن العمل." } }, "ca" : { @@ -17314,13 +17626,13 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Xcodes sucht and installiert in ein einzelnes Verzeichnis. Standard (und empfohlen beizubehalten) ist /Programme. Änderungen am Speicherort von Xcode können dazu führen, dass andere Apps/Dienste aufhören zu funktionieren. " + "value" : "Xcodes sucht und installiert in einem einzelnen Verzeichnis. Standard (und empfohlen beizubehalten) ist /Applications. Änderungen am Speicherort von Xcode können dazu führen, dass andere Apps/Dienste aufhören zu funktionieren." } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Το Xcodes αναζητά και εγκαθιστά σε ένα και μόνο φάκελο. Κατά προεπιλογή (όπως και συνίσταται) αυτός είναι ο «Εφαρμογές». Όποιες αλλαγές στην τοποθεσία του Xcode μπορεί να έχει ως αποτέλεσμα την προβληματική λειτουργία άλλων εφαρμογών/υπηρεσιών." + "value" : "Το Xcodes αναζητά και εγκαθιστά σε έναν φάκελο. Ο προεπιλεγμένος και συνιστώμενος φάκελος είναι ο /Applications. Οι αλλαγές στην τοποθεσία του Xcode μπορεί να προκαλέσουν προβλήματα στη λειτουργία άλλων εφαρμογών ή υπηρεσιών." } }, "en" : { @@ -17404,7 +17716,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Xcodes bir klasörü arayıp oraya yükler. Varsayılan(ve önerilen) yöntem /Uygulamalar klasöründe tutmaktır. Xcode'un bulunduğu ortamdaki herhangi bir değişiklik başka bir uygulamanın/servisin çalışmasını durdurabilir." + "value" : "Xcodes bir klasörü arayıp oraya yükler. Varsayılan (ve önerilen) yöntem /Applications klasöründe tutmaktır. Xcode'un bulunduğu ortamdaki herhangi bir değişiklik başka bir uygulamanın/servisin çalışmasını durdurabilir." } }, "uk" : { @@ -21194,7 +21506,7 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "On select, will keep the name as the version eg. Xcode-13.4.1.app" + "value" : "الاحتفاظ باسم التطبيق الحالي عند تعيينه كإصدار نشط، مثل \u2066Xcode-27.0.app\u2069." } }, "ca" : { @@ -21707,6 +22019,18 @@ }, "Open Browser" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فتح المتصفح" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เปิดเบราว์เซอร์" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -22203,6 +22527,18 @@ }, "Paste redirected URL" : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لصق عنوان URL بعد إعادة التوجيه" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "วาง URL หลังเปลี่ยนเส้นทาง" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -25556,7 +25892,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Show Open In Rosetta option" + "value" : "„Mit Rosetta öffnen“ anzeigen" } }, "el" : { @@ -25686,7 +26022,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Open in Rosetta option will show where other \"Open\" functions are available. Note : This will only show for Apple Silicon machines." + "value" : "Zeigt auf Macs mit Apple-Chip eine Option an, um Xcode mit Rosetta zu öffnen." } }, "el" : { @@ -25931,6 +26267,18 @@ }, "Signing out..." : { "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "جارٍ تسجيل الخروج…" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "กำลังออกจากระบบ…" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -27114,7 +27462,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unarchiving (This can take a while)" + "value" : "Extracting archive (this may take a while)" } }, "es" : { diff --git a/XcodesTests/PinCodeInputTests.swift b/XcodesTests/PinCodeInputTests.swift index 7acbd0ff..e9e6d567 100644 --- a/XcodesTests/PinCodeInputTests.swift +++ b/XcodesTests/PinCodeInputTests.swift @@ -73,6 +73,9 @@ struct PinCodeInputTests { #expect(textFields.filter { !$0.isEditable }.count == 6) #expect(inputField.contentType == .oneTimeCode) #expect(inputField.accessibilityLabel() == "Verification code") + let accessibleChildren = try #require(view.accessibilityChildren()) + #expect(accessibleChildren.count == 1) + #expect(accessibleChildren.first as? NSTextFieldCell === inputField.cell) #expect(textFields.filter { !$0.isEditable }.allSatisfy { !$0.isAccessibilityElement() }) } From 8fa602dccd000713a43b9fa117655d91455fdf41 Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 03:16:53 +0100 Subject: [PATCH 5/6] fix: corrected action context and contrast - Localised active-version help and exposed exact action targets. - Removed stale selection wording from detail controls. - Replaced fixed selection colours with adaptive system styling. - Removed obsolete highlight plumbing and corrected an Arabic translation. --- Xcodes/Backend/XcodeCommands.swift | 3 ++- .../InfoPane/InstalledStateButtons.swift | 1 - .../XcodeList/InstallationStepRowView.swift | 10 +-------- Xcodes/Frontend/XcodeList/XcodeListView.swift | 17 +++++++------- .../Frontend/XcodeList/XcodeListViewRow.swift | 22 +++++++------------ Xcodes/Resources/Localizable.xcstrings | 2 +- 6 files changed, 21 insertions(+), 34 deletions(-) diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index cfe8fccd..3e7eec03 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -110,7 +110,8 @@ struct SelectButton: View { } } .disabled(xcode?.selected != false) - .help("Select") + .accessibilityValue(xcode?.description ?? "") + .help(xcode?.selected == true ? Text("ActiveVersionDescription") : Text("MakeActiveVersionDescription")) } private func select() { diff --git a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift index d225a79e..ce653966 100644 --- a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift @@ -33,7 +33,6 @@ struct InstalledStateButtons: View { HStack { SelectButton(xcode: xcode) .disabled(xcode.selected) - .help("Selected") OpenButton(xcode: xcode) .help("Open") diff --git a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift index a6c0dcb3..d6558816 100644 --- a/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift +++ b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift @@ -3,7 +3,6 @@ import XcodesKit struct InstallationStepRowView: View { let installationStep: XcodeInstallationStep - let highlighted: Bool let cancel: () -> Void var body: some View { @@ -33,7 +32,7 @@ struct InstallationStepRowView: View { .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) - .foregroundColor(highlighted ? .white : .secondary) + .foregroundStyle(.primary) .help("StopInstallation") } .frame(minWidth: 80) @@ -49,37 +48,31 @@ struct InstallView_Previews: PreviewProvider { installationStep: .downloading( progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 } ), - highlighted: false, cancel: {} ) InstallationStepRowView( installationStep: .unarchiving, - highlighted: false, cancel: {} ) InstallationStepRowView( installationStep: .moving(destination: "/Applications"), - highlighted: false, cancel: {} ) InstallationStepRowView( installationStep: .trashingArchive, - highlighted: false, cancel: {} ) InstallationStepRowView( installationStep: .checkingSecurity, - highlighted: false, cancel: {} ) InstallationStepRowView( installationStep: .finishing, - highlighted: false, cancel: {} ) } @@ -94,7 +87,6 @@ struct InstallView_Previews: PreviewProvider { installationStep: .downloading( progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 } ), - highlighted: true, cancel: {} ) } diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index f22d3573..3a6f5c03 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -53,7 +53,6 @@ struct XcodeListView: View { ForEach(visibleXcodes) { entry in XcodeListViewRow( xcode: entry.xcode, - selected: selectedXcodeID == entry.xcode.id, appState: appState, latestReleaseForSelectedPrerelease: latestReleaseForSelectedPrerelease(entry.xcode) ) @@ -212,7 +211,6 @@ private struct GroupedXcodeListContent: View { ForEach(minorVersionGroup.versions) { entry in XcodeListViewRow( xcode: entry.xcode, - selected: selectedXcodeID == entry.xcode.id, appState: appState, latestReleaseForSelectedPrerelease: latestReleaseForSelectedPrerelease(entry.xcode) ) @@ -306,6 +304,7 @@ private struct XcodeVersionGroupRow: View { .contentShape(Rectangle()) } .accessibilityLabel(Text("MakeActiveVersionDescription")) + .accessibilityValue(latestSelectionTarget.description) .buttonStyle(PlainButtonStyle()) .help(staleSelectedHelpText(selectedVersion: selectedVersion, latestRelease: latestSelectableRelease, selectionTarget: latestSelectionTarget)) } else { @@ -335,27 +334,30 @@ private struct XcodeVersionGroupRow: View { .contentShape(Rectangle()) } .accessibilityLabel(Text("MakeActiveVersionDescription")) + .accessibilityValue(latestSelectionTarget.description) .buttonStyle(PlainButtonStyle()) .help("MakeActiveVersionDescription") } } private func staleSelectedHelpText(selectedVersion: Xcode, latestRelease: Xcode, selectionTarget: Xcode?) -> Text { + let status = Text("Active") + Text(verbatim: ": \(selectedVersion.description)\n") + + Text(verbatim: String(format: localizeString("LatestReleaseDescription"), latestRelease.description)) switch selectionTarget?.installState { case .installed: if let selectionTarget, selectionTarget.id != selectedVersion.id { - return Text(verbatim: "\(selectedVersion.description) selected, \(latestRelease.description) available. Click to select \(selectionTarget.description).") + return status + Text(verbatim: "\n") + Text("MakeActive") + Text(verbatim: ": \(selectionTarget.description)") } else { - return Text(verbatim: "\(selectedVersion.description) selected, \(latestRelease.description) available.") + return status } case .notInstalled: if let selectionTarget { - return Text(verbatim: "\(selectedVersion.description) selected, \(latestRelease.description) available. Install \(selectionTarget.description) to select it.") + return status + Text(verbatim: "\n") + Text("Install") + Text(verbatim: ": \(selectionTarget.description)") } else { - return Text(verbatim: "\(selectedVersion.description) selected, \(latestRelease.description) available.") + return status } case .installing, .uninstalling, .none: - return Text(verbatim: "\(selectedVersion.description) selected, \(latestRelease.description) available.") + return status } } @@ -365,7 +367,6 @@ private struct XcodeVersionGroupRow: View { case let .installing(installationStep) = installingVersion.installState { InstallationStepRowView( installationStep: installationStep, - highlighted: false, cancel: { appState.presentedAlert = .cancelInstall(xcode: installingVersion) } ) } else if let latestRelease { diff --git a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift index c98ec038..0488f778 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift @@ -5,13 +5,11 @@ import XcodesKit struct XcodeListViewRow: View { let xcode: Xcode - let selected: Bool let appState: AppState let latestReleaseForSelectedPrerelease: Xcode? - init(xcode: Xcode, selected: Bool, appState: AppState, latestReleaseForSelectedPrerelease: Xcode? = nil) { + init(xcode: Xcode, appState: AppState, latestReleaseForSelectedPrerelease: Xcode? = nil) { self.xcode = xcode - self.selected = selected self.appState = appState self.latestReleaseForSelectedPrerelease = latestReleaseForSelectedPrerelease } @@ -125,6 +123,7 @@ struct XcodeListViewRow: View { .contentShape(Rectangle()) } .labelStyle(.iconOnly) + .accessibilityValue(latestReleaseForSelectedPrerelease.description) .buttonStyle(PlainButtonStyle()) .help(staleSelectedHelpText) case .notInstalled: @@ -148,6 +147,7 @@ struct XcodeListViewRow: View { .contentShape(Rectangle()) } .labelStyle(.iconOnly) + .accessibilityValue(xcode.description) .buttonStyle(PlainButtonStyle()) .help("MakeActiveVersionDescription") } @@ -181,7 +181,6 @@ struct XcodeListViewRow: View { case let .installing(installationStep): InstallationStepRowView( installationStep: installationStep, - highlighted: selected, cancel: { appState.presentedAlert = .cancelInstall(xcode: xcode) } ) case .uninstalling: @@ -196,14 +195,15 @@ struct XcodeListViewRow: View { } private var staleSelectedHelpText: Text { - let selectedVersion = xcode.version.appleDescription - let latestVersion = latestReleaseForSelectedPrerelease?.version.appleDescription ?? "" + let latestVersion = latestReleaseForSelectedPrerelease?.description ?? "" + let status = Text("Active") + Text(verbatim: ": \(xcode.description)\n") + + Text(verbatim: String(format: localizeString("LatestReleaseDescription"), latestVersion)) switch latestReleaseForSelectedPrerelease?.installState { case .installed: - return Text(verbatim: "\(selectedVersion) selected, \(latestVersion) available. Click to select \(latestVersion).") + return status + Text(verbatim: "\n") + Text("MakeActive") + Text(verbatim: ": \(latestVersion)") case .notInstalled: - return Text(verbatim: "\(selectedVersion) selected, \(latestVersion) available. Install \(latestVersion) to select it.") + return status + Text(verbatim: "\n") + Text("Install") + Text(verbatim: ": \(latestVersion)") case .installing, .uninstalling, .none: return Text("ActiveVersionDescription") } @@ -215,37 +215,31 @@ struct XcodeListViewRow_Previews: PreviewProvider { Group { XcodeListViewRow( xcode: Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), - selected: false, appState: AppState() ) XcodeListViewRow( xcode: Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), - selected: false, appState: AppState() ) XcodeListViewRow( xcode: Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), - selected: false, appState: AppState() ) XcodeListViewRow( xcode: Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), - selected: false, appState: AppState() ) XcodeListViewRow( xcode: Xcode(version: Version("12.0.0+1234A")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), - selected: false, appState: AppState() ) XcodeListViewRow( xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), - selected: false, appState: AppState() ) } diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index b6229ed5..27c95892 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -18651,7 +18651,7 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Make active" + "value" : "تعيين كإصدار نشط" } }, "ca" : { From 6d5b252e2b00139dc4a955d5749cc5e813520f0b Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Wed, 23 Sep 2026 03:31:56 +0100 Subject: [PATCH 6/6] fix: exposed disclosure state - Added localised expanded and collapsed accessibility values across supported languages. - Preserved existing group actions and verified both state transitions without desktop activation. --- Xcodes/Frontend/XcodeList/XcodeListView.swift | 1 + Xcodes/Resources/Localizable.xcstrings | 260 ++++++++++++++++++ 2 files changed, 261 insertions(+) diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 3a6f5c03..c9ae7c30 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -267,6 +267,7 @@ private struct XcodeVersionGroupRow: View { .contentShape(Rectangle()) } .buttonStyle(.plain) + .accessibilityValue(isExpanded ? Text("Expanded") : Text("Collapsed")) selectControl .padding(.trailing, 4) diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index 27c95892..41339d80 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -1,6 +1,266 @@ { "sourceLanguage" : "en", "strings" : { + "Expanded" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expanded" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "موسّع" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expandit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgeklappt" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Αναπτυγμένο" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expandido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Laajennettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Développé" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "विस्तृत" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Espanso" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "展開" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "펼쳐짐" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uitgevouwen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rozwinięte" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expandido" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Развёрнуто" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ขยายอยู่" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genişletilmiş" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Розгорнуто" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已展开" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "已展開" + } + } + } + }, + "Collapsed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Collapsed" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مطوي" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contret" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eingeklappt" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Συμπτυγμένο" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contraído" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supistettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réduit" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "संकुचित" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compresso" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "折りたたみ" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "접힘" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Samengevouwen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zwinięte" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recolhido" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Свёрнуто" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ยุบอยู่" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daraltılmış" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Згорнуто" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已折叠" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "已收合" + } + } + } + }, "SignInFailure" : { "localizations" : { "en" : {