From 2596a7d41ef0310c3e5c1bb154c896d729ed5f0f Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Fri, 6 Nov 2026 10:21:10 -0500 Subject: [PATCH] Validate live catalog categories and add missing translations --- .github/workflows/build.yml | 2 + assets/Localizable.xcstrings | 64 ++++++++++++++++++++++++++++++ src/CMakeLists.txt | 1 + src/CatalogCategory.swift | 27 +++++++++++++ src/CatalogPresentation.swift | 16 -------- tests/CMakeLists.txt | 13 ++++++ tests/CatalogCategoriesCheck.swift | 47 ++++++++++++++++++++++ 7 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 src/CatalogCategory.swift create mode 100644 tests/CatalogCategoriesCheck.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c65fc75..fcc4c27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,6 +36,8 @@ jobs: if-no-files-found: error - name: Check Swift formatting run: xcrun swift-format lint --recursive --strict src tests + - name: Check catalog categories + run: cmake --build --preset release --target CheckCatalogCategories - name: Check localization catalog run: | cmake --build --preset release --target GenerateStrings diff --git a/assets/Localizable.xcstrings b/assets/Localizable.xcstrings index a73db7e..f6bd316 100644 --- a/assets/Localizable.xcstrings +++ b/assets/Localizable.xcstrings @@ -785,6 +785,38 @@ } } }, + "Games": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Games" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "游戏" + } + } + } + }, + "Health": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Health" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "健康" + } + } + } + }, "Install": { "localizations": { "en": { @@ -993,6 +1025,22 @@ } } }, + "Maps": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Maps" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "地图" + } + } + } + }, "Media": { "localizations": { "en": { @@ -1345,6 +1393,22 @@ } } }, + "Reading": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reading" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "阅读" + } + } + } + }, "Recipe Revision": { "localizations": { "en": { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3f37cc..a877bcd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(SimStore MACOSX_BUNDLE ContentView.swift LibrarySection.swift AppCatalog.swift + CatalogCategory.swift AppDownload.swift AppPackage.swift AppInstaller.swift diff --git a/src/CatalogCategory.swift b/src/CatalogCategory.swift new file mode 100644 index 0000000..b3e0cff --- /dev/null +++ b/src/CatalogCategory.swift @@ -0,0 +1,27 @@ +import Foundation + +enum CatalogCategory: String { + case browser, cloud, communication, games, health, maps, media, productivity, reading, security, + social, utilities + + var title: String { + switch self { + case .browser: String(localized: "Browser") + case .cloud: String(localized: "Cloud") + case .communication: String(localized: "Communication") + case .games: String(localized: "Games") + case .health: String(localized: "Health") + case .maps: String(localized: "Maps") + case .media: String(localized: "Media") + case .productivity: String(localized: "Productivity") + case .reading: String(localized: "Reading") + case .security: String(localized: "Security") + case .social: String(localized: "Social") + case .utilities: String(localized: "Utilities") + } + } + + static func title(_ category: String) -> String { + Self(rawValue: category)?.title ?? category + } +} diff --git a/src/CatalogPresentation.swift b/src/CatalogPresentation.swift index 1442000..0c36aae 100644 --- a/src/CatalogPresentation.swift +++ b/src/CatalogPresentation.swift @@ -1,21 +1,5 @@ import SwiftUI -enum CatalogCategory { - static func title(_ category: String) -> String { - switch category { - case "browser": String(localized: "Browser") - case "cloud": String(localized: "Cloud") - case "communication": String(localized: "Communication") - case "media": String(localized: "Media") - case "productivity": String(localized: "Productivity") - case "security": String(localized: "Security") - case "social": String(localized: "Social") - case "utilities": String(localized: "Utilities") - default: category - } - } -} - extension CatalogApp { var displayDescription: String { localizedDescription(preferredLanguages: Bundle.main.preferredLocalizations) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab37606..d48324c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,19 @@ target_compile_options(SimStoreCatalogTest PRIVATE -parse-as-library) add_test(NAME catalog COMMAND SimStoreCatalogTest) set_tests_properties(catalog PROPERTIES TIMEOUT 10) +add_executable(SimStoreCatalogCategoriesCheck EXCLUDE_FROM_ALL CatalogCategoriesCheck.swift + "${PROJECT_SOURCE_DIR}/src/AppCatalog.swift" + "${PROJECT_SOURCE_DIR}/src/AppConfiguration.swift" + "${PROJECT_SOURCE_DIR}/src/CatalogCategory.swift" +) +target_compile_options(SimStoreCatalogCategoriesCheck PRIVATE -parse-as-library) +add_custom_target(CheckCatalogCategories + COMMAND "$" + DEPENDS SimStoreCatalogCategoriesCheck + COMMENT "Checking category coverage in the latest app catalog" + VERBATIM +) + add_executable(SimStoreCatalogLoadingTest CatalogLoadingTests.swift "${PROJECT_SOURCE_DIR}/src/AppCatalog.swift" "${PROJECT_SOURCE_DIR}/src/CatalogClient.swift" diff --git a/tests/CatalogCategoriesCheck.swift b/tests/CatalogCategoriesCheck.swift new file mode 100644 index 0000000..35bb364 --- /dev/null +++ b/tests/CatalogCategoriesCheck.swift @@ -0,0 +1,47 @@ +import Foundation + +@main +struct CatalogCategoriesCheck { + static func main() async { + do { + let url = AppConfiguration.catalogURL + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) + request.timeoutInterval = 30 + let (data, response) = try await URLSession.shared.data(for: request) + guard let response = response as? HTTPURLResponse else { + throw failure("Expected an HTTP response from \(url).") + } + guard response.statusCode == 200 else { + throw failure("The latest catalog returned HTTP \(response.statusCode).") + } + let catalog = try AppCatalog.decode(data) + var missing: [String: [String]] = [:] + for app in catalog.apps { + for category in app.categories where CatalogCategory(rawValue: category) == nil { + missing[category, default: []].append(app.id) + } + } + guard missing.isEmpty else { + let details = missing.keys.sorted().map { category in + " \(category): \(missing[category]!.sorted().joined(separator: ", "))" + } + throw failure( + "Unlisted catalog categories:\n\(details.joined(separator: "\n"))\nAdd them to CatalogCategory and translate their titles." + ) + } + let categories = Set(catalog.apps.flatMap(\.categories)) + print( + "PASS: all \(categories.count) categories across \(catalog.apps.count) apps in \(url) are declared." + ) + } catch { + FileHandle.standardError.write( + Data("Catalog category check failed: \(error.localizedDescription)\n".utf8)) + exit(EXIT_FAILURE) + } + } + + private static func failure(_ message: String) -> NSError { + NSError( + domain: "CatalogCategoriesCheck", code: 1, userInfo: [NSLocalizedDescriptionKey: message]) + } +}