| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- import Foundation
- import Testing
- private let bundle = Bundle.main
- @Suite("Localization Tests", .serialized) struct LocalizationTests {
- @Test("No stray % inside format strings") func testNoStrayPercent() {
- // Array to collect strings with issues
- var offenders: [(lang: String, key: String, value: String, file: String)] = []
- // Regular expression patterns
- let placeholderPattern = "%[0-9]*\\$?[.,]?[0-9]*[a-zA-Z@]" // Matches placeholders like %@, %d, %1$@
- let escapedPercentPattern = "%%" // Matches escaped percent signs
- let percentPattern = "%" // Matches any percent sign
- // Compile regexes (force-unwrapped since patterns are static and valid)
- let placeholderRegex = try! NSRegularExpression(pattern: placeholderPattern)
- let escapedPercentRegex = try! NSRegularExpression(pattern: escapedPercentPattern)
- let percentRegex = try! NSRegularExpression(pattern: percentPattern)
- // Assume 'bundle' is accessible, e.g., Bundle.main
- for locale in bundle.localizations where locale != "Base" {
- guard let lproj = bundle.path(forResource: locale, ofType: "lproj"),
- let files = FileManager.default.enumerator(atPath: lproj) else { continue }
- // Iterate over .strings files in the localization directory
- for case let f as String in files where f.hasSuffix(".strings") {
- let path = (lproj as NSString).appendingPathComponent(f)
- guard let table = NSDictionary(contentsOfFile: path) as? [String: String] else { continue }
- // Check each key-value pair in the .strings file
- for (key, value) in table {
- let nsValue = value as NSString
- let range = NSRange(location: 0, length: nsValue.length)
- // Determine if the value contains any placeholders
- let hasPlaceholders = placeholderRegex.firstMatch(in: value, range: range) != nil
- // Only check for stray % if the value has placeholders
- if hasPlaceholders {
- // Find all ranges covered by placeholders and escaped %%
- let placeholderMatches = placeholderRegex.matches(in: value, range: range)
- let escapedMatches = escapedPercentRegex.matches(in: value, range: range)
- let coveredRanges = (placeholderMatches + escapedMatches).map(\.range)
- // Find all % signs in the value
- let percentMatches = percentRegex.matches(in: value, range: range)
- // Check each % to see if it's stray (not covered by a placeholder or %%)
- for percentMatch in percentMatches {
- let percentLocation = percentMatch.range.location
- let isCovered = coveredRanges.contains { NSLocationInRange(percentLocation, $0) }
- if !isCovered {
- offenders.append((lang: locale, key: key, value: value, file: f))
- break // Stop checking this string after finding an issue
- }
- }
- }
- // If no placeholders, skip the check (single % is allowed)
- }
- }
- }
- // Assert that no offenders were found using Testing's #expect
- #expect(
- offenders.isEmpty,
- """
- Found \(offenders.count) string(s) that still have a single % although \
- the value contains printf placeholders:
- \(offenders.map { "\($0.lang) – \($0.file)\n⟨key⟩ \($0.key)\n⟨value⟩ \($0.value)" }
- .joined(separator: "\n\n"))
- """
- )
- }
- }
|