From 8a16d8fb2947fe1dd727c59212fef5f8c48f34a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Leinha=CC=88upl?= Date: Tue, 30 Aug 2022 16:29:33 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20formatted?= =?UTF-8?q?=20plurals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/PluralRuleWrapper.swift | 17 +++++- .../ACKLocalization+Plurals.swift | 53 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) 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..be13fe2 100644 --- a/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift +++ b/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift @@ -96,4 +96,57 @@ final class ACKLocalizationPluralsTests: XCTestCase { XCTAssertEqual(error as? PluralError, PluralError.invalidPluralRule(rows[0].key)) } } + + func testPluralWithStringFormatSpecifier() throws { + 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) + + + let plurals = try! ackLocalization.buildPlurals(from: rows) + XCTAssertEqual(plurals.count, 1) + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let encodedData = try encoder.encode(plurals.first?.value) + + XCTAssertEqual(encodedData, expectedResultEncoded) + } + + func testPluralWithIntegerFormatSpecifier() throws { + 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) + + let plurals = try! ackLocalization.buildPlurals(from: rows) + XCTAssertEqual(plurals.count, 1) + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let encodedData = try encoder.encode(plurals.first?.value) + + XCTAssertEqual(encodedData, expectedResultEncoded) + } } From 85f7078f5088fc04b7a81f8241af74ae4012b65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Leinha=CC=88upl?= Date: Tue, 30 Aug 2022 16:33:38 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=84=20Update=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) 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 From 459b61b40ac58a64e84d1c04b95aa8eac0cd932a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Leinha=CC=88upl?= Date: Tue, 30 Aug 2022 16:36:02 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=85=20Update=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACKLocalization+Plurals.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift b/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift index be13fe2..66c9e30 100644 --- a/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift +++ b/Tests/ACKLocalizationCoreTests/ACKLocalization+Plurals.swift @@ -98,6 +98,7 @@ final class ACKLocalizationPluralsTests: XCTestCase { } func testPluralWithStringFormatSpecifier() throws { + // Given let rows = [ LocRow(key: "key##{many}", value: "%d many"), ] @@ -113,18 +114,18 @@ final class ACKLocalizationPluralsTests: XCTestCase { let expectedResultEncoded = try JSONSerialization .data(withJSONObject: expectedResult, options: .sortedKeys) - - let plurals = try! ackLocalization.buildPlurals(from: rows) - XCTAssertEqual(plurals.count, 1) - + // 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"), ] @@ -140,13 +141,13 @@ final class ACKLocalizationPluralsTests: XCTestCase { let expectedResultEncoded = try JSONSerialization .data(withJSONObject: expectedResult, options: .sortedKeys) - let plurals = try! ackLocalization.buildPlurals(from: rows) - XCTAssertEqual(plurals.count, 1) - + // 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) } }