diff --git a/CHANGELOG.md b/CHANGELOG.md index abce0ec..1d3b600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ## master +### Added +- Add support for formatted plurals ([#33](https://github.com/AckeeCZ/ACKLocalization/pull/33), kudos to @leinhauplk) + ## 1.3.0 ### Added diff --git a/Sources/ACKLocalizationCore/Model/PluralRuleWrapper.swift b/Sources/ACKLocalizationCore/Model/PluralRuleWrapper.swift index 9157a10..5a46a1d 100644 --- a/Sources/ACKLocalizationCore/Model/PluralRuleWrapper.swift +++ b/Sources/ACKLocalizationCore/Model/PluralRuleWrapper.swift @@ -15,12 +15,22 @@ struct PluralRuleWrapper { } extension PluralRuleWrapper: Codable { + private enum Constants { + static let stringFormatSpecifier = "%s" + } + func encode(to encoder: Encoder) throws { // Since the stringDict is completely dynamic // we cannot use some predefined `struct` to encapsulate the data. // We need to use `CustomKey` that can take any string as a key. var container = encoder.container(keyedBy: CustomKey.self) + // If the plural contains string format specifier, then use the positional arguments. + let containsStringSpecifier: Bool = translations.contains(where: { + $0.value.lowercased().contains(Constants.stringFormatSpecifier) + } ) + let formatKey = containsStringSpecifier ? "%1$#@inner@" : "%#@inner@" + var items = [ // Mandatory key and the only option "NSStringFormatSpecTypeKey": "NSStringPluralRuleType", @@ -28,14 +38,17 @@ extension PluralRuleWrapper: Codable { ] translations.forEach { - items[$0.key.rawValue] = $0.value + items[$0.key.rawValue] = $0.value.replacingOccurrences( + of: Constants.stringFormatSpecifier, + with: "%2$@" + ) } // StringsDicts use variables by default. // We need to specify the variable that is then // replaced by the plural rule. // In this case the variable is `inner` and it's hardcoded. - try container.encode("%#@inner@", forKey: .init(stringValue: "NSStringLocalizedFormatKey")) + try container.encode(formatKey, forKey: .init(stringValue: "NSStringLocalizedFormatKey")) try container.encode(items, forKey: .init(stringValue: "inner")) } } diff --git a/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift b/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift index 7bd181e..66c9e30 100644 --- a/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift +++ b/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift @@ -96,4 +96,58 @@ final class ACKLocalizationPluralsTests: XCTestCase { XCTAssertEqual(error as? PluralError, PluralError.invalidPluralRule(rows[0].key)) } } + + func testPluralWithStringFormatSpecifier() throws { + // Given + let rows = [ + LocRow(key: "key##{many}", value: "%d many"), + ] + + let expectedResult: [String: Encodable] = [ + "NSStringLocalizedFormatKey": "%#@inner@", + "inner": [ + "NSStringFormatSpecTypeKey": "NSStringPluralRuleType", + "NSStringFormatValueTypeKey": "d", + "many": "%d many" + ] + ] + let expectedResultEncoded = try JSONSerialization + .data(withJSONObject: expectedResult, options: .sortedKeys) + + // When + let plurals = try ackLocalization.buildPlurals(from: rows) + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let encodedData = try encoder.encode(plurals.first?.value) + + // Then + XCTAssertEqual(encodedData, expectedResultEncoded) + } + + func testPluralWithIntegerFormatSpecifier() throws { + // Given + let rows = [ + LocRow(key: "key##{many}", value: "%s many"), + ] + + let expectedResult: [String: Encodable] = [ + "NSStringLocalizedFormatKey": "%1$#@inner@", + "inner": [ + "NSStringFormatSpecTypeKey": "NSStringPluralRuleType", + "NSStringFormatValueTypeKey": "d", + "many": "%2$@ many" + ] + ] + let expectedResultEncoded = try JSONSerialization + .data(withJSONObject: expectedResult, options: .sortedKeys) + + // When + let plurals = try ackLocalization.buildPlurals(from: rows) + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let encodedData = try encoder.encode(plurals.first?.value) + + // Then + XCTAssertEqual(encodedData, expectedResultEncoded) + } }