A .strings value may span several physical lines, so deleting such an entry line by line leaves orphaned lines that the parser folds into the preceding value. Every key behind that point then disappears at runtime while plutil -lint still reports OK. Tab titles are looked up without a defaultValue:, so the tab bar rendered raw keys like "tab.overview" while the rest of the UI fell back to its English defaults. LocalizationIntegrityTests parses each table out of the app bundle and fails on multi-line values, on missing keys that have no fallback, and on leftover cable.pro.* entries. LocalizedTabBarUITests launches the app in German and asserts the tab bar is translated.
76 lines
2.6 KiB
Swift
76 lines
2.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import Cable
|
|
|
|
/// Guards the string tables themselves. A malformed `.strings` file still parses — a value
|
|
/// spanning several lines silently swallows the entries behind it — and the app then renders
|
|
/// raw keys like "tab.overview". These tests fail instead.
|
|
struct LocalizationIntegrityTests {
|
|
|
|
private static let locales = ["Base", "de", "es", "fr", "nl"]
|
|
|
|
/// Keys rendered without a `defaultValue:`, so a missing entry is visible to users.
|
|
private static let keysWithoutFallback = [
|
|
"tab.overview",
|
|
"tab.components",
|
|
"tab.batteries",
|
|
"tab.chargers",
|
|
]
|
|
|
|
private func table(for locale: String) throws -> [String: String] {
|
|
let bundle = Bundle.main
|
|
guard let url = bundle.url(
|
|
forResource: "Localizable",
|
|
withExtension: "strings",
|
|
subdirectory: nil,
|
|
localization: locale
|
|
) else {
|
|
throw LocalizationTestError.tableMissing(locale)
|
|
}
|
|
let data = try Data(contentsOf: url)
|
|
let parsed = try PropertyListSerialization.propertyList(from: data, format: nil)
|
|
guard let table = parsed as? [String: String] else {
|
|
throw LocalizationTestError.tableMalformed(locale)
|
|
}
|
|
return table
|
|
}
|
|
|
|
@Test func everyLocaleProvidesTheKeysThatHaveNoFallback() async throws {
|
|
for locale in Self.locales {
|
|
let table = try table(for: locale)
|
|
for key in Self.keysWithoutFallback {
|
|
#expect(table[key] != nil, "\(locale): missing \(key), the UI would show the raw key")
|
|
#expect(
|
|
table[key] != key,
|
|
"\(locale): \(key) is not translated"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func noValueSpansMultipleLines() async throws {
|
|
for locale in Self.locales {
|
|
let table = try table(for: locale)
|
|
for (key, value) in table {
|
|
#expect(
|
|
!value.contains("\n"),
|
|
"\(locale): \(key) contains a real newline; removing such an entry line by line corrupts the table"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func retiredProKeysAreGone() async throws {
|
|
for locale in Self.locales {
|
|
let table = try table(for: locale)
|
|
let leftovers = table.keys.filter { $0.hasPrefix("cable.pro.") }
|
|
#expect(leftovers.isEmpty, "\(locale): dead PRO keys \(leftovers.sorted())")
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum LocalizationTestError: Error {
|
|
case tableMissing(String)
|
|
case tableMalformed(String)
|
|
}
|