// Copyright (C) 2026 ysicing // SPDX-License-Identifier: AGPL-3.0-or-later import Foundation import Testing @testable import Localization struct LanguageSupportTests { @Test func containsExactlyNineLanguagesAndAutomaticDetection() { #expect(AppLanguage.allCases.count == 10) #expect(Set(AppLanguage.allCases.filter { $0 != .system }.map(\.languageCode)) == Set(["zh-Hans", "zh-Hant", "ja", "ko", "en", "de", "es", "fr", "ar"])) #expect(AppLanguage(rawValue: "chinese") == .chinese) #expect(AppLanguage(rawValue: "english") == .english) #expect(AppLanguage(rawValue: "system") == .system) } @Test func resolvesScriptsRegionsAndPreferenceOrder() { for value in ["zh-Hant", "zh-TW", "zh-HK", "zh_MO", "zh-Hant-CN"] { #expect(AppLanguage.resolve(preferredLanguages: [value]) == .traditionalChinese) } for value in ["zh-Hans", "zh-CN", "zh-SG", "zh-Hans-TW", "zh"] { #expect(AppLanguage.resolve(preferredLanguages: [value]) == .chinese) } #expect(AppLanguage.resolve(preferredLanguages: ["ru-RU", "ja-JP", "en"]) == .japanese) #expect(AppLanguage.resolve(preferredLanguages: ["es-MX", "zh-CN"]) == .spanish) #expect(AppLanguage.resolve(preferredLanguages: ["ar-EG"]) == .arabic) #expect(AppLanguage.resolve(preferredLanguages: ["ko-KR"]) == .korean) #expect(AppLanguage.resolve(preferredLanguages: ["de-CH"]) == .german) #expect(AppLanguage.resolve(preferredLanguages: ["fr-CA"]) == .french) #expect(AppLanguage.resolve(preferredLanguages: ["ru"]) == .english) #expect(AppLanguage.resolve(preferredLanguages: []) == .english) } @Test func searchesNativeChineseEnglishAndAccents() { #expect(AppLanguage.japanese.matches(search: "日语")) #expect(AppLanguage.japanese.matches(search: "日本")) #expect(AppLanguage.german.matches(search: "german")) #expect(AppLanguage.german.matches(search: "DEUTSCH")) #expect(AppLanguage.french.matches(search: "francais")) #expect(AppLanguage.spanish.matches(search: "espanol")) #expect(AppLanguage.arabic.matches(search: "العربية")) #expect(AppLanguage.traditionalChinese.matches(search: "繁体")) #expect(AppLanguage.system.matches(search: "auto")) #expect(AppLanguage.allCases.allSatisfy { $0.matches(search: " ") }) #expect(AppLanguage.allCases.contains { $0.matches(search: "zzzzzz") } == false) } @Test func setsProcessLocaleWithoutChangingOtherPreferences() { let name = "LanguageSupportTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! defer { defaults.removePersistentDomain(forName: name) } defaults.set("keep", forKey: "unrelated") AppLanguage.traditionalChinese.applyToProcessLocale(defaults: defaults) #expect(defaults.stringArray(forKey: "AppleLanguages") == ["zh-Hant"]) #expect(defaults.string(forKey: "AppleLocale")?.hasPrefix("zh_Hant_") == true) AppLanguage.arabic.applyToProcessLocale(defaults: defaults) #expect(defaults.stringArray(forKey: "AppleLanguages") == ["ar"]) AppLanguage.system.applyToProcessLocale(defaults: defaults) // 移除应用覆盖后 UserDefaults 会继承全局值,应检查自身域而不是合并后的查询结果。 #expect(defaults.persistentDomain(forName: name)?["AppleLanguages"] == nil) #expect(defaults.persistentDomain(forName: name)?["AppleLocale"] == nil) #expect(defaults.string(forKey: "unrelated") == "keep") #expect(AppLanguage.arabic.isRightToLeft) #expect(AppLanguage.french.isRightToLeft == false) #expect(L10n.locale(for: .french).language.languageCode?.identifier == "fr") } @Test func templatesPreserveLiteralBracesAndTranslateNestedValues() { let table = Translations(tables: ["内存\tMemory\n打开 {}\tOpen {}\n{} / {}\t{2} then {1}"]) #expect(table.translate("打开 config{}.json") == "Open config{}.json") #expect(table.translate("打开 内存") == "Open Memory") #expect(table.translate("first / second") == "second then first") let rtl = Translations(tables: ["地址:{}\tالعنوان: {}"], rightToLeft: true) #expect(rtl.translate("地址:192.0.2.1") == "العنوان: \u{2068}192.0.2.1\u{2069}") } @Test func rejectsBrokenPlaceholdersAndEmptyTranslations() { let table = Translations(tables: ["下载 {}\tDownload\n{} / {}\t{1} twice {1}\n设置\t\n内存\tMemory"]) #expect(table.sourceKeys == ["内存"]) #expect(table.translate("下载 file.zip") == nil) } @Test(arguments: AppLanguage.allCases.filter { $0 != .system && $0 != .chinese }) func everyLanguageHasCompleteCatalog(_ language: AppLanguage) throws { let catalog = try #require(Translations.catalog(for: language)) #expect(catalog.sourceKeys == Translations.shared.sourceKeys) for key in ["语言", "应用 UI 语言", "自动检测", "搜索语言", "没有匹配的语言", "热压力:正常", "热压力:偏高", "热压力:严重", "热压力:临界", "热压力:未知", "高效处理多线程任务", "由 macOS 报告的系统热状态,与 CPU 温度读数独立。", "核心最高温度;余量以 100°C 为参考,不代表设备实际降频阈值。", "亮度", "对比度", "音量", "显示器 {} 台", "重新检测显示器控制", "显示器不支持此控制", "暂时无法读取,请检查 DDC/CI 与连接", "显示链路正在恢复…", "修改未确认,请重新检测", "合并显示", "仅图标", "面板项目", "启动中…", "正在启动风扇…", "只显示 XStats 图标,点击查看已选项目的状态总览", "所选项目显示在弹出面板中,原有图标风格会保留。", "打开进程监控", "额度显示", "数字单位", "货币", "估算费用", "30 天", "按小时统计", "按天统计", "暂无小时明细,请刷新用量。", "套餐:Pro", "套餐有效期至:2026-10-20", "可用重置次数:3", "重置次数最早到期:2026-10-05", "AI 用量与额度共用此间隔;新数据到达后更新菜单栏。", "价格来源:models.dev;人民币按缓存汇率换算。", "按模型基础 API 单价估算,不含阶梯加价,非订阅账单。", "设置", "内存", "下载并应用", "中国大陆节假日与调休", "黄历", "每日", "每周", "累计", "XStats 基于 OpenStats 开发,感谢原项目的开源贡献 · MIT License"] { #expect(catalog.translate(key)?.isEmpty == false) } } }