Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions Sources/SkipFoundation/PropertyListSerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,8 @@ public class PropertyListSerialization {
let categories = ["zero", "one", "two", "few", "many", "other"]
let rules = categories.compactMap { (category: String) -> String? in
guard let pluralString = varDict[category] as? String else { return nil }
var cleaned = pluralString!
for index in 1...10 {
cleaned = cleaned
.replacingOccurrences(of: "%\(index)$@", with: "{\(index-1)}")
.replacingOccurrences(of: "%\(index)$d", with: "{\(index-1)}")
}
let icuCategory = category == "zero" ? "=0" : category
return "\(icuCategory){\(cleaned)}"
return "\(icuCategory){\(icuMessage(fromFormat: pluralString!))}"
}.joined(separator: " ")

if !rules.isEmpty {
Expand All @@ -140,6 +134,43 @@ public class PropertyListSerialization {
return result
}

/// Converts a printf-style plural form into ICU MessageFormat text: each specifier becomes an argument
/// (`%lld` and `%1$d` become `{0,number,#}`, without grouping like Darwin, and `%@` becomes `{0}`),
/// and ICU syntax characters in the literal text are quoted.
static func icuMessage(fromFormat format: String) -> String {
let specifier = java.util.regex.Pattern.compile("%(?:(\\d+)\\$)?[-+ 0#']*\\d*(?:\\.\\d+)?(?:hh|h|ll|l|q|z|t|j)?([@dDiuUxXoOfFeEgGaAcCsSp%])")
let matcher = specifier.matcher(format)
var result = ""
var literalStart = 0
var nextArgument = 0
while matcher.find() {
result += icuLiteral(format.substring(literalStart, matcher.start()))
literalStart = matcher.end()
let conversion = matcher.group(2)!
if conversion == "%" {
result += "%"
continue
}
var argument = nextArgument
if let position = matcher.group(1) {
argument = position.toInt() - 1
} else {
nextArgument += 1
}
result += "dDiuU".contains(conversion) ? "{\(argument),number,#}" : "{\(argument)}"
}
result += icuLiteral(format.substring(literalStart))
return result
}

private static func icuLiteral(_ text: String) -> String {
text.replace("'", "''")
.replace("{", "'{'")
.replace("}", "'}'")
.replace("#", "'#'")
.replace("|", "'|'")
}

private static func openStepPropertyList(from data: Data, options: PropertyListSerialization.ReadOptions = []) throws -> [String: String]? {
var dict: Dictionary<String, String> = [:]

Expand Down
41 changes: 41 additions & 0 deletions Tests/SkipFoundationTests/Foundation/LocaleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,47 @@ final class LocaleTests: XCTestCase {
XCTAssertNotNil(dict["repeats_every_day"])
}

func testLocalizableStringsDictPrintfSpecifiers() throws {
#if !SKIP
throw XCTSkip("Darwin formats .stringsdict plurals natively")
#else
let locstr = #"""
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>%lld files</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@value@</string>
<key>value</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lld</string>
<key>zero</key>
<string>No files</string>
<key>one</key>
<string>%lld file, it's #1</string>
<key>other</key>
<string>%lld files {100%%}</string>
</dict>
</dict>
</dict>
</plist>
"""#

let data = try XCTUnwrap(locstr.data(using: .utf8))
let plist = try XCTUnwrap(PropertyListSerialization.propertyList(from: data, options: [], format: nil))
let format = try XCTUnwrap(plist["%lld files"])

XCTAssertEqual(String.localizedStringWithFormat(format, 0), "No files")
XCTAssertEqual(String.localizedStringWithFormat(format, 1), "1 file, it's #1")
XCTAssertEqual(String.localizedStringWithFormat(format, 1234), "1234 files {100%}")
#endif
}

func testLocaleFormats() throws {
#if !SKIP
// TODO
Expand Down
Loading