Files
Termind/TermindTests/AppLanguageTests.swift
T

179 lines
8.7 KiB
Swift

import Foundation
import Testing
@testable import Termind
struct AppLanguageTests {
@Test
func missingAndUnknownPreferencesFollowSystem() throws {
let defaults = try makeDefaults()
#expect(AppLanguage.stored(in: defaults) == .system)
defaults.set("unsupported", forKey: SettingsKey.appLanguage)
#expect(AppLanguage.stored(in: defaults) == .system)
}
@Test
func explicitLanguagesWriteFullAppleLanguageTags() throws {
let defaults = try makeDefaults()
AppLanguage.store(.english, in: defaults)
#expect(AppLanguage.stored(in: defaults) == .english)
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["en"])
AppLanguage.store(.simplifiedChineseChina, in: defaults)
#expect(AppLanguage.stored(in: defaults) == .simplifiedChineseChina)
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["zh-Hans-CN"])
#expect(AppLanguage.simplifiedChineseChina.locale.identifier == "zh-Hans-CN")
}
@Test
func followSystemRemovesAppleLanguageOverride() throws {
let suiteName = "AppLanguageTests.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defaults.removePersistentDomain(forName: suiteName)
defaults.set(["zh-Hans-SG"], forKey: "AppleLanguages")
AppLanguage.store(.system, in: defaults)
#expect(AppLanguage.stored(in: defaults) == .system)
#expect(defaults.persistentDomain(forName: suiteName)?["AppleLanguages"] == nil)
}
@Test
func everyCatalogEntryHasSimplifiedChinese() throws {
let repository = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent().deletingLastPathComponent()
for name in ["Localizable", "InfoPlist"] {
let url = repository.appending(path: "Termind/Resources/\(name).xcstrings")
let root = try #require(
JSONSerialization.jsonObject(with: Data(contentsOf: url))
as? [String: Any]
)
let strings = try #require(root["strings"] as? [String: Any])
for (key, value) in strings {
let entry = try #require(value as? [String: Any])
if entry["shouldTranslate"] as? Bool == false { continue }
let localizations = try #require(entry["localizations"] as? [String: Any])
let chinese = try #require(localizations["zh-Hans"] as? [String: Any])
let units = localizedUnits(in: chinese)
#expect(!units.isEmpty, "No translated values: \(key)")
for unit in units {
#expect(unit["state"] as? String == "translated", "Incomplete: \(key)")
#expect(!(unit["value"] as? String ?? "").isEmpty || key.isEmpty)
}
}
}
}
@Test
func editorLabelsAndForwardingMessagesHaveChineseTranslations() throws {
let repository = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent().deletingLastPathComponent()
let url = repository.appending(path: "Termind/Resources/Localizable.xcstrings")
let root = try #require(
JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any]
)
let strings = try #require(root["strings"] as? [String: Any])
let labels = [
"New Host", "Edit Host", "Import Key", "Generate Key", "Edit Key", "Export to Hosts",
"New Group", "Edit Group", "New Snippet", "Edit Snippet", "New Package", "Edit Package",
"New Identity", "Edit Identity", "Add Hosts", "Close",
"New Port Forward", "Edit Port Forward", "Back", "Start", "Stop",
"SOCKS Listen Port", "Listen Port", "Listen Address on This Mac",
"Listen Address on SSH Host", "SOCKS Listen Address on This Mac",
"Destination Host on This Mac", "Destination Host from SSH Host",
"Unable to create an SSH forwarding session.", "SSH handshake failed",
"Unable to start remote forwarding", "The forwarding SSH connection closed",
]
let interpolations: [String: [String]] = [
"%@ → listen %@ → This Mac → %@:%@": ["jump.example", "[::1]:8080", "db.internal", "5432"],
"SOCKS5 %@ → %@ → Requested Target": ["[::1]:1080", "jump.example"],
"SSH %@ → This Mac %@": ["[::1]:8080", "[2001:db8::1]:443"],
"Unable to listen on %@: %@": ["[::1]:8080", "Address already in use"],
]
for key in labels + interpolations.keys.sorted() {
let entry = try #require(strings[key] as? [String: Any], "Missing editor or forwarding string: \(key)")
let localizations = try #require(entry["localizations"] as? [String: Any])
let chinese = try #require(localizations["zh-Hans"] as? [String: Any])
let unit = try #require(chinese["stringUnit"] as? [String: Any])
let translation = try #require(unit["value"] as? String)
#expect(unit["state"] as? String == "translated")
#expect(translation.range(of: "\\p{Han}", options: .regularExpression) != nil)
if let arguments = interpolations[key] {
let placeholders = translation.matches(of: /%(?:[1-9][0-9]*\$)?@/)
#expect(placeholders.count == arguments.count, "Placeholder mismatch: \(key)")
let rendered = String(format: translation, arguments: arguments)
var remaining = rendered[...]
for argument in arguments {
let range = try #require(remaining.range(of: argument), "Lost or reordered value: \(key)")
remaining = remaining[range.upperBound...]
}
}
}
}
@Test
func automaticModelCaptionResolvesInChineseAndPreservesModelAndProvider() throws {
let path = try #require(Bundle.main.path(forResource: "zh-Hans", ofType: "lproj"))
let bundle = try #require(Bundle(path: path))
let detail = "用于对话、工具和助手侧边栏。"
let model = "model-example"
let provider = "Example Provider"
let caption = String(localized: "\(detail) Currently \(model) on \(provider).", bundle: bundle)
#expect(caption == "用于对话、工具和助手侧边栏。当前使用Example Provider提供的model-example。")
}
@Test
func countedToolResourcesUseCatalogPluralRules() throws {
let repository = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent().deletingLastPathComponent()
let url = repository.appending(path: "Termind/Resources/Localizable.xcstrings")
let root = try #require(
JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any]
)
let strings = try #require(root["strings"] as? [String: Any])
for key in [
// `%lld results` is not here: `web_search`'s card moved to AgentKit
// with the runtime, and so did the plural resource behind it.
"%lld hosts", "%lld items", "%lld questions",
"%lld tool calls", "%@ — %lld tools",
] {
let entry = try #require(strings[key] as? [String: Any])
let localizations = try #require(entry["localizations"] as? [String: Any])
let english = try #require(localizations["en"] as? [String: Any])
let chinese = try #require(localizations["zh-Hans"] as? [String: Any])
#expect(pluralForms(in: english).keys.sorted() == ["one", "other"])
#expect(pluralForms(in: chinese).keys.sorted() == ["other"])
}
}
private func localizedUnits(in value: [String: Any]) -> [[String: Any]] {
if let unit = value["stringUnit"] as? [String: Any] { return [unit] }
guard let variations = value["variations"] as? [String: Any] else { return [] }
return variations.values.flatMap { variation -> [[String: Any]] in
guard let forms = variation as? [String: Any] else { return [] }
return forms.values.flatMap { form -> [[String: Any]] in
guard let form = form as? [String: Any] else { return [] }
return localizedUnits(in: form)
}
}
}
private func pluralForms(in localization: [String: Any]) -> [String: Any] {
let variations = localization["variations"] as? [String: Any]
return variations?["plural"] as? [String: Any] ?? [:]
}
private func makeDefaults() throws -> UserDefaults {
let suiteName = "AppLanguageTests.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defaults.removePersistentDomain(forName: suiteName)
return defaults
}
}