618 lines
26 KiB
Swift
618 lines
26 KiB
Swift
import Foundation
|
||
import Testing
|
||
@testable import CodexBar
|
||
|
||
struct LocalizationLanguageCatalogTests {
|
||
private let languageKeys = [
|
||
"language_system",
|
||
"language_english",
|
||
"language_german",
|
||
"language_spanish",
|
||
"language_catalan",
|
||
"language_chinese_simplified",
|
||
"language_chinese_traditional",
|
||
"language_portuguese_brazilian",
|
||
"language_swedish",
|
||
"language_french",
|
||
"language_dutch",
|
||
"language_ukrainian",
|
||
"language_russian",
|
||
"language_italian",
|
||
"language_vietnamese",
|
||
"language_japanese",
|
||
"language_korean",
|
||
"language_turkish",
|
||
"language_indonesian",
|
||
"language_polish",
|
||
"language_arabic",
|
||
"language_persian",
|
||
"language_thai",
|
||
"language_galician",
|
||
]
|
||
|
||
@Test
|
||
func `app language catalog includes Ukrainian`() {
|
||
#expect(AppLanguage.allCases.contains(.ukrainian))
|
||
#expect(AppLanguage.ukrainian.rawValue == "uk")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Russian`() {
|
||
#expect(AppLanguage.allCases.contains(.russian))
|
||
#expect(AppLanguage.russian.rawValue == "ru")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Korean`() {
|
||
#expect(AppLanguage.allCases.contains(.korean))
|
||
#expect(AppLanguage.korean.rawValue == "ko")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Turkish`() {
|
||
#expect(AppLanguage.allCases.contains(.turkish))
|
||
#expect(AppLanguage.turkish.rawValue == "tr")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Italian`() {
|
||
#expect(AppLanguage.allCases.contains(.italian))
|
||
#expect(AppLanguage.italian.rawValue == "it")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Indonesian`() {
|
||
#expect(AppLanguage.allCases.contains(.indonesian))
|
||
#expect(AppLanguage.indonesian.rawValue == "id")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Polish`() {
|
||
#expect(AppLanguage.allCases.contains(.polish))
|
||
#expect(AppLanguage.polish.rawValue == "pl")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Arabic Persian and Thai`() {
|
||
#expect(AppLanguage.arabic.rawValue == "ar")
|
||
#expect(AppLanguage.persian.rawValue == "fa")
|
||
#expect(AppLanguage.thai.rawValue == "th")
|
||
}
|
||
|
||
@Test
|
||
func `app language catalog includes Galician`() {
|
||
#expect(AppLanguage.allCases.contains(.galician))
|
||
#expect(AppLanguage.galician.rawValue == "gl")
|
||
}
|
||
|
||
@Test
|
||
func `language picker labels use stable native names`() {
|
||
let expected: [AppLanguage: String] = [
|
||
.system: "System",
|
||
.english: "English",
|
||
.chineseSimplified: "简体中文",
|
||
.chineseTraditional: "繁體中文",
|
||
.japanese: "日本語",
|
||
.spanish: "Español",
|
||
.portugueseBrazilian: "Português (Brasil)",
|
||
.korean: "한국어",
|
||
.german: "Deutsch",
|
||
.french: "Français",
|
||
.arabic: "العربية",
|
||
.italian: "Italiano",
|
||
.vietnamese: "Tiếng Việt",
|
||
.dutch: "Nederlands",
|
||
.turkish: "Türkçe",
|
||
.ukrainian: "Українська",
|
||
.russian: "Русский",
|
||
.indonesian: "Bahasa Indonesia",
|
||
.polish: "Polski",
|
||
.persian: "فارسی",
|
||
.thai: "ไทย",
|
||
.galician: "Galego",
|
||
.catalan: "Català",
|
||
.swedish: "Svenska",
|
||
]
|
||
|
||
#expect(expected.count == AppLanguage.allCases.count)
|
||
|
||
let japaneseLabels = CodexBarLocalizationOverride.$appLanguage.withValue("ja") {
|
||
Dictionary(uniqueKeysWithValues: AppLanguage.allCases.map { ($0, $0.label) })
|
||
}
|
||
let arabicLabels = CodexBarLocalizationOverride.$appLanguage.withValue("ar") {
|
||
Dictionary(uniqueKeysWithValues: AppLanguage.allCases.map { ($0, $0.label) })
|
||
}
|
||
|
||
#expect(japaneseLabels == expected)
|
||
#expect(arabicLabels == expected)
|
||
}
|
||
|
||
@Test
|
||
func `system language preserves an external Apple Languages override`() {
|
||
Self.withTemporaryDefaults(for: #function) { defaults, _ in
|
||
defaults.set(["de"], forKey: "AppleLanguages")
|
||
|
||
AppLanguagePreferenceMigration.clearLegacyOverrideIfOwned(
|
||
storedAppLanguage: "",
|
||
defaults: defaults)
|
||
|
||
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["de"])
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `matching legacy language override is cleared`() {
|
||
Self.withTemporaryDefaults(for: #function) { defaults, suiteName in
|
||
defaults.set(["ja"], forKey: "AppleLanguages")
|
||
|
||
AppLanguagePreferenceMigration.clearLegacyOverrideIfOwned(
|
||
storedAppLanguage: "ja",
|
||
defaults: defaults)
|
||
|
||
#expect(defaults.persistentDomain(forName: suiteName)?["AppleLanguages"] == nil)
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `unrelated external language override is preserved`() {
|
||
Self.withTemporaryDefaults(for: #function) { defaults, _ in
|
||
defaults.set(["de"], forKey: "AppleLanguages")
|
||
|
||
AppLanguagePreferenceMigration.clearLegacyOverrideIfOwned(
|
||
storedAppLanguage: "ja",
|
||
defaults: defaults)
|
||
|
||
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["de"])
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `new language bundles include representative native labels`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let expectations: [String: [String: String]] = [
|
||
"ar": [
|
||
"language_arabic": "العربية",
|
||
"tab_general": "عام",
|
||
"quit_app": "إنهاء CodexBar",
|
||
"usage_percent_suffix_left": "متبقٍ",
|
||
],
|
||
"fa": [
|
||
"language_persian": "فارسی",
|
||
"tab_general": "عمومی",
|
||
"quit_app": "خروج از CodexBar",
|
||
"usage_percent_suffix_left": "باقی مانده",
|
||
],
|
||
"th": [
|
||
"language_thai": "ไทย",
|
||
"tab_general": "ทั่วไป",
|
||
"quit_app": "ออกจาก CodexBar",
|
||
"usage_percent_suffix_left": "คงเหลือ",
|
||
],
|
||
"ru": [
|
||
"language_russian": "Русский",
|
||
"tab_general": "Общие",
|
||
"quit_app": "Выйти из CodexBar",
|
||
"usage_percent_suffix_left": "осталось",
|
||
],
|
||
"gl": [
|
||
"language_galician": "Galego",
|
||
"tab_general": "Xeral",
|
||
"quit_app": "Saír de CodexBar",
|
||
"terminal_app_title": "Terminal predeterminado",
|
||
"terminal_app_subtitle": "Terminal usado pola acción Abrir terminal",
|
||
],
|
||
"ca": [
|
||
"A managed Codex login is already running. Wait for it to finish before adding ":
|
||
"Ja hi ha un inici de sessió gestionat de Codex en curs. Espereu que acabi abans d'afegir ",
|
||
"%@: %@": "%@: %@",
|
||
"Sign in with Claude Code...": "Inicia sessió amb Claude Code...",
|
||
"keychain_access_caption":
|
||
"Desactiveu totes les lectures i escriptures del Clauer. " +
|
||
"Feu-ho si macOS continua mostrant sol·licituds de «Chrome/Brave/Edge Safe Storage» " +
|
||
"fins i tot després de triar «Permet sempre». La importació de galetes del navegador no " +
|
||
"estarà disponible mentre aquesta opció estigui activada; enganxeu manualment les " +
|
||
"capçaleres Cookie a Proveïdors. L'OAuth de Claude/Codex mitjançant la CLI continuarà funcionant.",
|
||
"language_catalan": "Català",
|
||
"menu_bar_metric_subtitle_mistral":
|
||
"Trieu entre la despesa de l'API de Mistral i l'ús del Monthly Plan per a la barra de menús.",
|
||
"quota_warning_notifications_subtitle":
|
||
"Avisa quan la quota restant de sessió o setmanal baixa per sota dels llindars configurats.",
|
||
"refresh_on_open_subtitle":
|
||
"Obté l'ús més recent de cada proveïdor cada vegada que obriu el menú.",
|
||
],
|
||
]
|
||
|
||
for (locale, expectedValues) in expectations {
|
||
let url = resourcesURL.appendingPathComponent("\(locale).lproj/Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: url) as? [String: String])
|
||
for (key, expectedValue) in expectedValues {
|
||
#expect(catalog[key] == expectedValue, "\(locale).\(key)")
|
||
}
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `german manual action labels do not describe a handbook`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let germanURL = root.appendingPathComponent("Sources/CodexBar/Resources/de.lproj/Localizable.strings")
|
||
let german = try #require(NSDictionary(contentsOf: germanURL) as? [String: String])
|
||
|
||
#expect(german["Manual"] == "Manuell")
|
||
#expect(german["refresh_manual"] == "Manuell")
|
||
}
|
||
|
||
@Test
|
||
func `galician localization matches the English catalog`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let englishURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let galicianURL = resourcesURL.appendingPathComponent("gl.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: englishURL) as? [String: String])
|
||
let galician = try #require(NSDictionary(contentsOf: galicianURL) as? [String: String])
|
||
|
||
#expect(Set(galician.keys) == Set(english.keys))
|
||
}
|
||
|
||
@Test
|
||
func `catalan localization matches the English catalog`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let englishURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let catalanURL = resourcesURL.appendingPathComponent("ca.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: englishURL) as? [String: String])
|
||
let catalan = try #require(NSDictionary(contentsOf: catalanURL) as? [String: String])
|
||
|
||
#expect(Set(catalan.keys) == Set(english.keys))
|
||
let statusFormat = try #require(catalan["%@: %@"])
|
||
#expect(String(format: statusFormat, "Quota", "42") == "Quota: 42")
|
||
}
|
||
|
||
@Test
|
||
func `localized catalogs include every app language label`() throws {
|
||
#expect(self.languageKeys.count == AppLanguage.allCases.count)
|
||
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let catalogs = try FileManager.default.contentsOfDirectory(
|
||
at: resourcesURL,
|
||
includingPropertiesForKeys: nil)
|
||
.filter { $0.pathExtension == "lproj" }
|
||
|
||
for catalogURL in catalogs {
|
||
let stringsURL = catalogURL.appendingPathComponent("Localizable.strings")
|
||
let contents = try String(contentsOf: stringsURL, encoding: .utf8)
|
||
for key in self.languageKeys {
|
||
#expect(contents.contains("\"\(key)\""), "Missing \(key) in \(catalogURL.lastPathComponent)")
|
||
}
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `localized catalogs include workday pace setting copy`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let catalogs = try FileManager.default.contentsOfDirectory(
|
||
at: resourcesURL,
|
||
includingPropertiesForKeys: nil)
|
||
.filter { $0.pathExtension == "lproj" }
|
||
|
||
for catalogURL in catalogs {
|
||
let stringsURL = catalogURL.appendingPathComponent("Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: stringsURL) as? [String: String])
|
||
let title = catalog["weekly_progress_work_days_title"]?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let subtitle = catalog["weekly_progress_work_days_subtitle"]?
|
||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
|
||
#expect(title?.isEmpty == false, "Missing workday title in \(catalogURL.lastPathComponent)")
|
||
#expect(subtitle?.isEmpty == false, "Missing workday subtitle in \(catalogURL.lastPathComponent)")
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `localized catalogs include default terminal setting copy`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let catalogs = try FileManager.default.contentsOfDirectory(
|
||
at: resourcesURL,
|
||
includingPropertiesForKeys: nil)
|
||
.filter { $0.pathExtension == "lproj" }
|
||
|
||
for catalogURL in catalogs {
|
||
let stringsURL = catalogURL.appendingPathComponent("Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: stringsURL) as? [String: String])
|
||
let title = catalog["terminal_app_title"]?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let subtitle = catalog["terminal_app_subtitle"]?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
|
||
#expect(title?.isEmpty == false, "Missing default terminal title in \(catalogURL.lastPathComponent)")
|
||
#expect(subtitle?.isEmpty == false, "Missing default terminal subtitle in \(catalogURL.lastPathComponent)")
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `ukrainian localization bundle exists and contains key UI labels`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let ukURL = root.appendingPathComponent("Sources/CodexBar/Resources/uk.lproj/Localizable.strings")
|
||
let contents = try String(contentsOf: ukURL, encoding: .utf8)
|
||
|
||
let requiredKeys = [
|
||
"\"language_title\"",
|
||
"\"language_subtitle\"",
|
||
"\"language_system\"",
|
||
"\"language_ukrainian\"",
|
||
"\"tab_general\"",
|
||
"\"quit_app\"",
|
||
]
|
||
for key in requiredKeys {
|
||
#expect(contents.contains(key), "Missing localization key: \(key)")
|
||
}
|
||
}
|
||
|
||
@Test
|
||
func `korean localization bundle includes representative native labels`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let koURL = root.appendingPathComponent("Sources/CodexBar/Resources/ko.lproj/Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: koURL) as? [String: String])
|
||
|
||
#expect(catalog["language_korean"] == "한국어")
|
||
#expect(catalog["tab_general"] == "일반")
|
||
#expect(catalog["quota_warning_session"] == "세션")
|
||
#expect(catalog["quota_warning_warn_at"] == "경고 기준")
|
||
#expect(catalog["quit_app"] == "CodexBar 종료")
|
||
}
|
||
|
||
@Test
|
||
func `turkish localization matches English catalog and preserves format placeholders`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let enURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let trURL = resourcesURL.appendingPathComponent("tr.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: enURL) as? [String: String])
|
||
let turkish = try #require(NSDictionary(contentsOf: trURL) as? [String: String])
|
||
|
||
#expect(Set(turkish.keys) == Set(english.keys))
|
||
#expect(turkish["language_turkish"] == "Türkçe")
|
||
#expect(turkish["tab_general"] == "Genel")
|
||
#expect(turkish["quit_app"] == "CodexBar'dan Çık")
|
||
#expect(turkish["display_mode_percent_desc"]?.contains("%45") == true)
|
||
#expect(turkish["session_depleted_notification_body"]?.hasPrefix("0% kaldı.") == true)
|
||
|
||
let format = try #require(turkish["quota_warning_notification_body"])
|
||
let rendered = String(
|
||
format: format,
|
||
locale: Locale(identifier: "tr_TR"),
|
||
arguments: ["%20", 15, "oturum"])
|
||
#expect(rendered.contains("15%"))
|
||
#expect(!rendered.contains("%2$d"))
|
||
|
||
let historyFormat = try #require(turkish["%@: %@%% used"])
|
||
let historyLabel = String(
|
||
format: historyFormat,
|
||
locale: Locale(identifier: "tr_TR"),
|
||
arguments: ["12 Haz", "45"])
|
||
#expect(historyLabel == "12 Haz: 45% kullanıldı")
|
||
|
||
let miniMaxFormat = try #require(turkish["minimax_used_percent_format"])
|
||
let miniMaxLabel = String(
|
||
format: miniMaxFormat,
|
||
locale: Locale(identifier: "tr_TR"),
|
||
arguments: ["45%"])
|
||
#expect(miniMaxLabel == "45% kullanıldı")
|
||
}
|
||
|
||
@Test
|
||
func `italian localization matches English catalog and includes current UI labels`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let enURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let itURL = resourcesURL.appendingPathComponent("it.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: enURL) as? [String: String])
|
||
let italian = try #require(NSDictionary(contentsOf: itURL) as? [String: String])
|
||
|
||
#expect(Set(italian.keys) == Set(english.keys))
|
||
#expect(italian["Individual credits"] == "Crediti individuali")
|
||
#expect(italian["Workspace"] == "Spazio di lavoro")
|
||
#expect(italian["display_mode_reset_time"] == "Ora di reimpostazione")
|
||
#expect(italian["display_mode_reset_time_desc"]?.contains("↻ 15:56") == true)
|
||
#expect(italian["ory_session_…=…; csrftoken=…"] == "ory_session_…=…; csrftoken=…")
|
||
#expect(italian["quota_warning_notifications_subtitle"]?.contains("scende sotto") == true)
|
||
#expect(italian["metric_mistral_payg"] == "A consumo")
|
||
#expect(italian["metric_mistral_monthly_plan"] == "Piano mensile")
|
||
|
||
let intentionallyUnchanged: Set = [
|
||
"Account",
|
||
"Build",
|
||
"Chrome",
|
||
"Cookie: ...",
|
||
"Cookie: …",
|
||
"Deployment",
|
||
"Email",
|
||
"Endpoint",
|
||
"Gemini Flash",
|
||
"GitHub",
|
||
"Google OAuth",
|
||
"No",
|
||
"Oasis-Token",
|
||
"Password",
|
||
"Provider",
|
||
"Token",
|
||
"%@: %@",
|
||
"byte_unit_byte",
|
||
"byte_unit_gigabyte",
|
||
"byte_unit_kilobyte",
|
||
"byte_unit_megabyte",
|
||
"language_arabic",
|
||
"language_galician",
|
||
"language_italian",
|
||
"language_persian",
|
||
"language_russian",
|
||
"language_thai",
|
||
"link_email",
|
||
"link_github",
|
||
"ory_session_…=…; csrftoken=…",
|
||
"section_privacy",
|
||
"tab_menu",
|
||
]
|
||
let unchanged = Set(english.keys.filter { italian[$0] == english[$0] })
|
||
#expect(unchanged == intentionallyUnchanged)
|
||
|
||
let warningFormat = try #require(italian["quota_warning_notification_body"])
|
||
let warning = String(
|
||
format: warningFormat,
|
||
locale: Locale(identifier: "it_IT"),
|
||
arguments: ["20%", 15, "settimanale"])
|
||
#expect(warning == "Rimane 20%. Hai raggiunto la soglia di avviso del 15% per la quota settimanale.")
|
||
|
||
let titleFormat = try #require(italian["quota_warning_notification_title"])
|
||
let title = String(
|
||
format: titleFormat,
|
||
locale: Locale(identifier: "it_IT"),
|
||
arguments: ["Codex", "settimanale"])
|
||
#expect(title == "Quota settimanale di Codex quasi esaurita")
|
||
}
|
||
|
||
@Test
|
||
func `indonesian localization matches English catalog and preserves format placeholders`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let enURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let idURL = resourcesURL.appendingPathComponent("id.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: enURL) as? [String: String])
|
||
let indonesian = try #require(NSDictionary(contentsOf: idURL) as? [String: String])
|
||
|
||
#expect(Set(indonesian.keys) == Set(english.keys))
|
||
#expect(indonesian["language_indonesian"] == "Bahasa Indonesia")
|
||
#expect(indonesian["tab_general"] == "Umum")
|
||
#expect(indonesian["quit_app"] == "Keluar CodexBar")
|
||
#expect(indonesian["30d"] == "30 hari")
|
||
#expect(indonesian["On"] == "Aktif")
|
||
#expect(indonesian["Off"] == "Nonaktif")
|
||
|
||
let warningFormat = try #require(indonesian["quota_warning_notification_body"])
|
||
let warning = String(
|
||
format: warningFormat,
|
||
locale: Locale(identifier: "id_ID"),
|
||
arguments: ["20%", 15, "sesi"])
|
||
#expect(warning.contains("15%"))
|
||
#expect(!warning.contains("%2$d"))
|
||
|
||
let historyFormat = try #require(indonesian["%@: %@%% used"])
|
||
let historyLabel = String(
|
||
format: historyFormat,
|
||
locale: Locale(identifier: "id_ID"),
|
||
arguments: ["12 Jun", "45"])
|
||
#expect(historyLabel == "12 Jun: 45% terpakai")
|
||
|
||
let daysFormat = try #require(indonesian["%dd"])
|
||
let daysLabel = String(
|
||
format: daysFormat,
|
||
locale: Locale(identifier: "id_ID"),
|
||
arguments: [30])
|
||
#expect(daysLabel == "30 hari")
|
||
}
|
||
|
||
@Test
|
||
func `polish localization matches English catalog and includes current UI labels`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let resourcesURL = root.appendingPathComponent("Sources/CodexBar/Resources")
|
||
let enURL = resourcesURL.appendingPathComponent("en.lproj/Localizable.strings")
|
||
let plURL = resourcesURL.appendingPathComponent("pl.lproj/Localizable.strings")
|
||
let english = try #require(NSDictionary(contentsOf: enURL) as? [String: String])
|
||
let polish = try #require(NSDictionary(contentsOf: plURL) as? [String: String])
|
||
|
||
#expect(Set(polish.keys) == Set(english.keys))
|
||
#expect(polish["Individual credits"] == "Kredyty indywidualne")
|
||
#expect(polish["Workspace"] == "Obszar roboczy")
|
||
#expect(polish["display_mode_reset_time"] == "Godzina resetu")
|
||
#expect(polish["display_mode_reset_time_desc"]?.contains("↻ 15:56") == true)
|
||
}
|
||
|
||
@Test
|
||
func `japanese usage chart accessibility text preserves argument meanings`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let jaURL = root.appendingPathComponent("Sources/CodexBar/Resources/ja.lproj/Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: jaURL) as? [String: String])
|
||
let format = try #require(catalog["%d days of usage data across %d services"])
|
||
|
||
let rendered = String(
|
||
format: format,
|
||
locale: Locale(identifier: "ja_JP"),
|
||
arguments: [7, 3])
|
||
|
||
#expect(rendered.contains("7日間"))
|
||
#expect(rendered.contains("3サービス"))
|
||
}
|
||
|
||
@Test
|
||
func `korean usage chart accessibility text preserves argument meanings`() throws {
|
||
let root = URL(fileURLWithPath: #filePath)
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
.deletingLastPathComponent()
|
||
let koURL = root.appendingPathComponent("Sources/CodexBar/Resources/ko.lproj/Localizable.strings")
|
||
let catalog = try #require(NSDictionary(contentsOf: koURL) as? [String: String])
|
||
let format = try #require(catalog["%d days of usage data across %d services"])
|
||
|
||
let rendered = String(
|
||
format: format,
|
||
locale: Locale(identifier: "ko_KR"),
|
||
arguments: [7, 3])
|
||
|
||
#expect(rendered.contains("7일간"))
|
||
#expect(rendered.contains("3개 서비스"))
|
||
}
|
||
|
||
private static func withTemporaryDefaults(
|
||
for testName: String,
|
||
_ body: (UserDefaults, String) -> Void)
|
||
{
|
||
let suiteName = "LocalizationLanguageCatalogTests.\(testName).\(UUID().uuidString)"
|
||
let defaults = UserDefaults(suiteName: suiteName)!
|
||
defaults.removePersistentDomain(forName: suiteName)
|
||
defer { defaults.removePersistentDomain(forName: suiteName) }
|
||
body(defaults, suiteName)
|
||
}
|
||
}
|