Skip to content

Commit 6f7f077

Browse files
committed
Restrict Apple plural forms to named categories and fold =0 into zero
populateForms merged every authored form key wholesale, so an ICU =N exact match (e.g. =0) went into the XLIFF as a =N quantity key, which the Apple plural formats don't accept. Keep the locale's CLDR forms plus any authored named category (zero/one/two/few/many/other) and drop the =N keys. When a plural has an =0 but no explicit zero, surface it as zero, which is the closest thing stringsdict/xcstrings can express.
1 parent abc0cd5 commit 6f7f077

2 files changed

Lines changed: 58 additions & 5 deletions

File tree

backend/data/src/main/kotlin/io/tolgee/formats/apple/out/AppleXliffExporter.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.tolgee.formats.PossiblePluralConversionResult
55
import io.tolgee.formats.apple.APPLE_CORRESPONDING_STRINGS_FILE_ORIGINAL
66
import io.tolgee.formats.apple.APPLE_FILE_ORIGINAL_CUSTOM_KEY
77
import io.tolgee.formats.apple.APPLE_PLURAL_PROPERTY_CUSTOM_KEY
8+
import io.tolgee.formats.formKeywords
89
import io.tolgee.formats.getPluralFormsForLocale
910
import io.tolgee.formats.xliff.model.XliffFile
1011
import io.tolgee.formats.xliff.model.XliffModel
@@ -233,12 +234,19 @@ class AppleXliffExporter(
233234
conversionResult: PossiblePluralConversionResult?,
234235
): Map<String, String> {
235236
val forms = conversionResult?.formsResult ?: return emptyMap()
236-
// Apple's plural formats (stringsdict/xcstrings) accept any form the user actually wrote
237-
// (e.g. "zero"), not just the forms CLDR defines for the target locale, so we keep every
238-
// authored form in addition to filling in the ones CLDR expects (e.g. "many" for cs).
239237
val otherForm = forms["other"] ?: ""
240-
val allForms = getPluralFormsForLocale(languageTag) + forms.keys
241-
return allForms.associateWith { forms[it] ?: otherForm }
238+
// Apple's plural formats (stringsdict/xcstrings) take the CLDR forms for the locale plus any
239+
// named category the user actually wrote (e.g. "zero" for en), but only the six named
240+
// categories — Apple has no equivalent for ICU "=N" exact matches, so those are dropped.
241+
val authoredNamedForms = forms.keys.filter { it in formKeywords }
242+
val allForms = getPluralFormsForLocale(languageTag) + authoredNamedForms
243+
val result = allForms.associateWithTo(mutableMapOf()) { forms[it] ?: otherForm }
244+
// Apple can't express an "=0" exact match, so if the user wrote one and there is no explicit
245+
// "zero", surface it as "zero" — the closest the format has.
246+
if ("zero" !in result) {
247+
forms["=0"]?.let { result["zero"] = it }
248+
}
249+
return result
242250
}
243251

244252
private fun getResultXliffFile(

backend/data/src/test/kotlin/io/tolgee/unit/formats/apple/out/AppleXliffFileExporterTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,51 @@ class AppleXliffFileExporterTest {
353353
)
354354
}
355355

356+
@Test
357+
fun `folds an explicit =0 into the zero form Apple can express`() {
358+
val built =
359+
buildExportTranslationList {
360+
add(
361+
languageTag = "en",
362+
keyName = "attendee_count",
363+
text = "{count, plural, =0 {No participants} one {1 Participant} other {Many participants}}",
364+
) {
365+
key.isPlural = true
366+
key.custom = mapOf(APPLE_FILE_ORIGINAL_CUSTOM_KEY to "Localizable.xcstrings")
367+
}
368+
}
369+
val exporter = getExporter(built.translations, emptyList())
370+
val data = getExported(exporter)
371+
data.assertFile(
372+
"en.xliff",
373+
"""
374+
|<?xml version="1.0" encoding="UTF-8" standalone="no"?>
375+
|<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
376+
| <file datatype="plaintext" original="Localizable.xcstrings" source-language="tag" target-language="en">
377+
| <header>
378+
| <tool tool-id="tolgee.io" tool-name="Tolgee"/>
379+
| </header>
380+
| <body>
381+
| <trans-unit id="attendee_count|==|plural.one">
382+
| <source xml:space="preserve"/>
383+
| <target xml:space="preserve">1 Participant</target>
384+
| </trans-unit>
385+
| <trans-unit id="attendee_count|==|plural.other">
386+
| <source xml:space="preserve"/>
387+
| <target xml:space="preserve">Many participants</target>
388+
| </trans-unit>
389+
| <trans-unit id="attendee_count|==|plural.zero">
390+
| <source xml:space="preserve"/>
391+
| <target xml:space="preserve">No participants</target>
392+
| </trans-unit>
393+
| </body>
394+
| </file>
395+
|</xliff>
396+
|
397+
""".trimMargin(),
398+
)
399+
}
400+
356401
@Test
357402
fun `honors the provided fileStructureTemplate`() {
358403
val exporter =

0 commit comments

Comments
 (0)