Skip to content

fix: preserve ICU exact-match plural selectors (=0, =1) in export#3548

Open
peterzen wants to merge 1 commit intotolgee:mainfrom
peterzen:fix/icu-plural-exact-match-ordering
Open

fix: preserve ICU exact-match plural selectors (=0, =1) in export#3548
peterzen wants to merge 1 commit intotolgee:mainfrom
peterzen:fix/icu-plural-exact-match-ordering

Conversation

@peterzen
Copy link
Copy Markdown

@peterzen peterzen commented Mar 19, 2026

Two related issues caused push→pull round-trips to produce file diffs:

  1. IcuToGenericFormatMessageConvertor.convert() called toIcuPluralString() without optimize=false, relying on the default optimize=true. This triggered optimizePluralForms + orderPluralForms on every export, silently reordering or removing plural forms that the user authored.

  2. orderPluralForms sorted unknown keys (including =0, =1, =2 exact-match selectors) alphabetically after 'other', placing them last instead of first. ICU exact-match selectors should precede keyword selectors and appear in ascending numeric order.

Fix: pass optimize=false in the export path so the stored ICU string is emitted verbatim. Also update orderPluralForms to sort =N selectors before keyword forms in numeric order so that explicit optimize calls produce canonical output consistent with the ICU specification.

Fixes: #3267

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed plural form ordering for translations with exact-match selectors (e.g., =1, =0). These selectors now correctly appear before keyword-based plural forms and are sorted in ascending numeric order.

Two related issues caused push→pull round-trips to produce file diffs:

1. IcuToGenericFormatMessageConvertor.convert() called toIcuPluralString()
   without optimize=false, relying on the default optimize=true. This
   triggered optimizePluralForms + orderPluralForms on every export,
   silently reordering or removing plural forms that the user authored.

2. orderPluralForms sorted unknown keys (including =0, =1, =2 exact-match
   selectors) alphabetically after 'other', placing them last instead of
   first. ICU exact-match selectors should precede keyword selectors and
   appear in ascending numeric order.

Fix: pass optimize=false in the export path so the stored ICU string is
emitted verbatim. Also update orderPluralForms to sort =N selectors before
keyword forms in numeric order so that explicit optimize calls produce
canonical output consistent with the ICU specification.

Fixes: tolgee#3267

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

These changes fix plural form handling by preventing automatic optimization of plural forms with exact-match selectors (e.g., =0, =1) and implementing proper ordering to preserve their sequence during import and export operations.

Changes

Cohort / File(s) Summary
Plural Form Ordering Logic
backend/data/src/main/kotlin/io/tolgee/formats/pluralFormsUtil.kt, backend/data/src/main/kotlin/io/tolgee/formats/generic/IcuToGenericFormatMessageConvertor.kt
Refactored orderPluralForms to implement two-level sorting: exact-match selectors (keys starting with =) sorted numerically first, followed by keyword forms sorted by formKeywords position. Disabled automatic optimization in conversion path by passing optimize = false to toIcuPluralString.
Plural Form Tests
backend/data/src/test/kotlin/io/tolgee/unit/formats/PluralsFormUtilTest.kt
Added comprehensive test coverage for exact-match selector ordering, numeric sorting of multiple =N forms, preservation of exact-match selectors during optimization, and round-trip parsing/serialization validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Anty0
  • JanCizmar

Poem

🐰 A plural tale of ordering true,
Exact matches now sort first in view,
No optimization shadows the way,
Forms preserved both night and day,
Tolgee's translations shine bright and new!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve ICU exact-match plural selectors (=0, =1) in export' clearly and concisely summarizes the main change: preserving exact-match selectors in plural exports.
Linked Issues check ✅ Passed The PR directly addresses issue #3267 by fixing both root causes: disabling optimization on export (optimize=false) and correcting orderPluralForms to place exact-match selectors before keywords in numeric order.
Out of Scope Changes check ✅ Passed All changes are scoped to the exact-match plural selector preservation fix: modified export behavior, updated sorting logic, and added comprehensive test coverage for the new behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/data/src/test/kotlin/io/tolgee/unit/formats/PluralsFormUtilTest.kt (1)

56-61: Consider using a single combined assertion for clearer test failure messages.

The separate assert calls could be consolidated to improve readability and provide a more informative failure message.

♻️ Optional: Combine ordering assertions
-    assert(output.indexOf("=0") < output.indexOf("one")) {
-      "=0 should come before 'one' in: $output"
-    }
-    assert(output.indexOf("=0") < output.indexOf("other")) {
-      "=0 should come before 'other' in: $output"
-    }
+    val idx0 = output.indexOf("=0")
+    val idxOne = output.indexOf("one")
+    val idxOther = output.indexOf("other")
+    assert(idx0 < idxOne && idx0 < idxOther) {
+      "=0 (at $idx0) should come before 'one' (at $idxOne) and 'other' (at $idxOther) in: $output"
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/data/src/test/kotlin/io/tolgee/unit/formats/PluralsFormUtilTest.kt`
around lines 56 - 61, Replace the two separate assert calls that check ordering
of "=0" vs "one" and "other" with a single combined assertion in
PluralsFormUtilTest that verifies both conditions at once (e.g. check
output.indexOf("=0") < output.indexOf("one") && output.indexOf("=0") <
output.indexOf("other")) and provide a clear failure message that includes the
output and the three indices so test failures show the full ordering info;
update the assertion surrounding the variable output accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/data/src/test/kotlin/io/tolgee/unit/formats/PluralsFormUtilTest.kt`:
- Around line 56-61: Replace the two separate assert calls that check ordering
of "=0" vs "one" and "other" with a single combined assertion in
PluralsFormUtilTest that verifies both conditions at once (e.g. check
output.indexOf("=0") < output.indexOf("one") && output.indexOf("=0") <
output.indexOf("other")) and provide a clear failure message that includes the
output and the three indices so test failures show the full ordering info;
update the assertion surrounding the variable output accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72b94f01-7844-4b10-8f40-3d2eaf2917b0

📥 Commits

Reviewing files that changed from the base of the PR and between b7ad9f2 and 847a382.

📒 Files selected for processing (3)
  • backend/data/src/main/kotlin/io/tolgee/formats/generic/IcuToGenericFormatMessageConvertor.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/pluralFormsUtil.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/PluralsFormUtilTest.kt

@JanCizmar JanCizmar requested a review from Anty0 March 27, 2026 12:21
@JanCizmar
Copy link
Copy Markdown
Member

Hey! Thanks a lot for the PR. @Anty0 can you please review this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Translation with plurals corrupted/changed after import

2 participants