From 10997157e00582da2c8f090c3e2627ac5c6f702b Mon Sep 17 00:00:00 2001 From: William Mead Date: Mon, 12 Jan 2026 14:45:43 +0100 Subject: [PATCH 1/3] Added ordered and validated strings --- Sources/TranslateKitBusiness/TK+Label.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Sources/TranslateKitBusiness/TK+Label.swift b/Sources/TranslateKitBusiness/TK+Label.swift index 5b2009d..74ca063 100644 --- a/Sources/TranslateKitBusiness/TK+Label.swift +++ b/Sources/TranslateKitBusiness/TK+Label.swift @@ -187,6 +187,16 @@ extension TK.Label { String(localized: "TK.Label.meeting", defaultValue: "Meeting", bundle: .module, comment: "Use for a scheduled business meeting or appointment") } + /// "Ordered" - Use for showing an order's status + public static var ordered: String { + String( + localized: "TK.Label.ordered", + defaultValue: "Ordered", + bundle: .module, + comment: "Use for showing an order's status" + ) + } + /// "Organization" - Use for company or group organization details public static var organization: String { String( @@ -247,6 +257,16 @@ extension TK.Label { ) } + /// "Validated" - Use for showing a validated status + public static var validated: String { + String( + localized: "TK.Label.validated", + defaultValue: "Validated", + bundle: .module, + comment: "Use for showing a validated status" + ) + } + /// "Vendor" - Use for referring to a supplier or service provider public static var vendor: String { String(localized: "TK.Label.vendor", defaultValue: "Vendor", bundle: .module, comment: "Use for referring to a supplier or service provider") From ab4bbc6cc3d169ac4e6408991d2bbad859abb7ca Mon Sep 17 00:00:00 2001 From: William Mead Date: Mon, 12 Jan 2026 14:50:41 +0100 Subject: [PATCH 2/3] Updated localizations --- .../Localizable.xcstrings | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Sources/TranslateKitBusiness/Localizable.xcstrings b/Sources/TranslateKitBusiness/Localizable.xcstrings index 72d426b..b9ff34b 100644 --- a/Sources/TranslateKitBusiness/Localizable.xcstrings +++ b/Sources/TranslateKitBusiness/Localizable.xcstrings @@ -15312,6 +15312,24 @@ } } }, + "TK.Label.ordered" : { + "comment" : "Use for showing an order's status", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Ordered" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commandé" + } + } + } + }, "TK.Label.organization" : { "comment" : "Use for company or group organization details", "localizations" : { @@ -16818,6 +16836,24 @@ } } }, + "TK.Label.validated" : { + "comment" : "Use for showing a validated status", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Validated" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validé" + } + } + } + }, "TK.Label.vendor" : { "comment" : "Use for referring to a supplier or service provider", "localizations" : { From c5f11c56e3810827d40109e86b8413d78df7a9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Mon, 12 Oct 2026 09:02:13 +0200 Subject: [PATCH 3/3] Translate the order and validation status labels The two business labels arrived with English and French only, so every other language fell back to English. They now ship in all 40 languages of the catalog, with comments that tell a translator they are statuses. A test reads the compiled catalog and fails for any language that lacks one of the two keys. --- Package.swift | 2 +- .../Localizable.xcstrings | 478 +++++++++++++++++- Sources/TranslateKitBusiness/TK+Label.swift | 8 +- .../BusinessStatusLabelTests.swift | 18 + Tests/TranslateKitTests/CompiledCatalog.swift | 41 ++ 5 files changed, 536 insertions(+), 11 deletions(-) create mode 100644 Tests/TranslateKitTests/BusinessStatusLabelTests.swift create mode 100644 Tests/TranslateKitTests/CompiledCatalog.swift diff --git a/Package.swift b/Package.swift index 22400ca..7343685 100644 --- a/Package.swift +++ b/Package.swift @@ -171,7 +171,7 @@ let package = Package( ), .testTarget( name: "TranslateKitTests", - dependencies: ["TranslateKit", "TranslateKitFinance"] + dependencies: ["TranslateKit", "TranslateKitBusiness", "TranslateKitFinance"] ), ] ) diff --git a/Sources/TranslateKitBusiness/Localizable.xcstrings b/Sources/TranslateKitBusiness/Localizable.xcstrings index b9ff34b..4006d09 100644 --- a/Sources/TranslateKitBusiness/Localizable.xcstrings +++ b/Sources/TranslateKitBusiness/Localizable.xcstrings @@ -15313,20 +15313,253 @@ } }, "TK.Label.ordered" : { - "comment" : "Use for showing an order's status", - "extractionState" : "extracted_with_value", + "comment" : "Use as a status showing that something has been ordered", "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم الطلب" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поръчано" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Encarregat" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objednáno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bestilt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bestellt" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Παραγγέλθηκε" + } + }, "en" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Ordered" } }, + "en-AU" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ordered" + } + }, + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ordered" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pedido" + } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pedido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilattu" + } + }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Commandé" } + }, + "fr-CA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commandé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הוזמן" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "ऑर्डर किया गया" + } + }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Naručeno" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megrendelve" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dipesan" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ordinato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "注文済み" + } + }, + "kk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тапсырыс берілді" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "주문됨" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dipesan" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bestilt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Besteld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zamówiono" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pedido feito" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Encomendado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comandat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Заказано" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objednané" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beställd" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "สั่งซื้อแล้ว" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sipariş Verildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замовлено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã đặt hàng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已下单" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "已下單" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "已下單" + } } } }, @@ -16837,20 +17070,253 @@ } }, "TK.Label.validated" : { - "comment" : "Use for showing a validated status", - "extractionState" : "extracted_with_value", + "comment" : "Use as a status showing that something has been checked and approved", "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم التحقق" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проверено" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validat" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ověřeno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Valideret" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validiert" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Επικυρώθηκε" + } + }, "en" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Validated" } }, + "en-AU" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validated" + } + }, + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validated" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validado" + } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tarkistettu" + } + }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Validé" } + }, + "fr-CA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אומת" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "सत्यापित" + } + }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Provjereno" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ellenőrizve" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Divalidasi" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Convalidato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "検証済み" + } + }, + "kk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тексерілді" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "검증됨" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disahkan" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validert" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gevalideerd" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zweryfikowano" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validado" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проверено" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Overené" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validerad" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ตรวจสอบแล้ว" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doğrulandı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перевірено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã xác thực" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已验证" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "已驗證" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "已驗證" + } } } }, diff --git a/Sources/TranslateKitBusiness/TK+Label.swift b/Sources/TranslateKitBusiness/TK+Label.swift index 74ca063..f731e53 100644 --- a/Sources/TranslateKitBusiness/TK+Label.swift +++ b/Sources/TranslateKitBusiness/TK+Label.swift @@ -187,13 +187,13 @@ extension TK.Label { String(localized: "TK.Label.meeting", defaultValue: "Meeting", bundle: .module, comment: "Use for a scheduled business meeting or appointment") } - /// "Ordered" - Use for showing an order's status + /// "Ordered" - Use as a status showing that something has been ordered public static var ordered: String { String( localized: "TK.Label.ordered", defaultValue: "Ordered", bundle: .module, - comment: "Use for showing an order's status" + comment: "Use as a status showing that something has been ordered" ) } @@ -257,13 +257,13 @@ extension TK.Label { ) } - /// "Validated" - Use for showing a validated status + /// "Validated" - Use as a status showing that something has been checked and approved public static var validated: String { String( localized: "TK.Label.validated", defaultValue: "Validated", bundle: .module, - comment: "Use for showing a validated status" + comment: "Use as a status showing that something has been checked and approved" ) } diff --git a/Tests/TranslateKitTests/BusinessStatusLabelTests.swift b/Tests/TranslateKitTests/BusinessStatusLabelTests.swift new file mode 100644 index 0000000..173e400 --- /dev/null +++ b/Tests/TranslateKitTests/BusinessStatusLabelTests.swift @@ -0,0 +1,18 @@ +import Testing + +import TranslateKitBusiness + +@Suite("Business status labels") +struct BusinessStatusLabelTests { + @Test func statusLabelsUseTheirEnglishSource() { + #expect(TK.Label.ordered == "Ordered") + #expect(TK.Label.validated == "Validated") + } + + @Test(arguments: ["TK.Label.ordered", "TK.Label.validated"]) + func statusLabelIsTranslatedIntoEveryLanguage(key: String) throws { + let bundle = try CompiledCatalog.bundle(for: "TranslateKitBusiness") + #expect(try CompiledCatalog.languages(in: bundle).count >= 40) + #expect(try CompiledCatalog.languagesMissing(key, inTarget: "TranslateKitBusiness") == []) + } +} diff --git a/Tests/TranslateKitTests/CompiledCatalog.swift b/Tests/TranslateKitTests/CompiledCatalog.swift new file mode 100644 index 0000000..061502b --- /dev/null +++ b/Tests/TranslateKitTests/CompiledCatalog.swift @@ -0,0 +1,41 @@ +import Foundation + +/// Reads the compiled String Catalog of a target the way an app sees it at runtime: one `.lproj` per language. +enum CompiledCatalog { + /// Placeholder returned for a key that a language does not contain. + static let missing = "__missing__" + + /// Locates the resource bundle SwiftPM places next to the test bundle for the given target. + static func bundle(for target: String) throws -> Bundle { + let bundleName = "TranslateKit_\(target).bundle" + let searchDirectories = ([Bundle(for: Token.self)] + Bundle.allBundles + [Bundle.main]).map { $0.bundleURL.deletingLastPathComponent() } + + for directory in searchDirectories { + if let bundle = Bundle(url: directory.appendingPathComponent(bundleName)) { return bundle } + } + throw BundleNotFound(name: bundleName) + } + + /// Every language the compiled catalog ships, read from its `.lproj` folders. + static func languages(in bundle: Bundle) throws -> [String] { + try FileManager.default.contentsOfDirectory(atPath: bundle.resourcePath ?? bundle.bundlePath) + .filter { $0.hasSuffix(".lproj") && $0 != "Base.lproj" } + .map { String($0.dropLast(".lproj".count)) } + .sorted() + } + + /// The value of `key` in one language, or `missing` when that language lacks it. + static func value(of key: String, in bundle: Bundle, language: String) -> String { + guard let path = bundle.path(forResource: language, ofType: "lproj"), let languageBundle = Bundle(path: path) else { return missing } + return languageBundle.localizedString(forKey: key, value: missing, table: nil) + } + + /// Languages of the target's catalog in which `key` has no value. + static func languagesMissing(_ key: String, inTarget target: String) throws -> [String] { + let bundle = try bundle(for: target) + return try languages(in: bundle).filter { value(of: key, in: bundle, language: $0) == missing } + } + + private struct BundleNotFound: Error { let name: String } + private final class Token {} +}