项目文件夹

文件
2026-07-13 12:22:33 +08:00

618 行
26 KiB
Swift

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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)
}
}