Skip to content

Convert every printf specifier in .stringsdict plural forms to ICU arguments - #134

Merged
piercifani merged 1 commit into
skiptools:mainfrom
piercifani:plural-printf-specifiers
Oct 8, 2026
Merged

piercifani merged 1 commit into
skiptools:mainfrom
piercifani:plural-printf-specifiers

Conversation

@piercifani

@piercifani piercifani commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #102, from the discussion in skiptools/skip-android-bridge#39.

The .stringsdict → ICU MessageFormat conversion only mapped positional %N$@ and %N$d to {N-1}. Xcode writes String Catalog plural variations with non-positional specifiers by default ("%lld files"), and those passed through as literal text, so String.localizedStringWithFormat rendered "%lld files". Literal {, }, ' or # in a plural form also changed the meaning of the ICU pattern, and braces made MessageFormat throw.

Each plural form is now converted specifier by specifier:

  • Integer conversions (%d, %i, %u, with any length modifier, positional or not) become {n,number,#}, which formats without grouping, like Darwin (1234 files, not 1,234 files).
  • Other conversions (%@, %f, …) become {n}.
  • Non-positional specifiers are numbered in order. Positional %N$… keeps its index.
  • %% becomes %, and ICU syntax characters in the literal text are quoted.

Plural selection is unchanged: MessageFormat with the current locale, and zero as =0.

This makes localizedStringWithFormat the single plural path on Android. skiptools/skip-android-bridge#39 will call it for the native String(localized:) instead of parsing the pattern itself.

Testing

  • New testLocalizableStringsDictPrintfSpecifiers (Robolectric): a %lld form with zero/one/other, an apostrophe and # in one form, braces and %% in another, and 1234 without grouping. Before this change it fails with IllegalArgumentException on the braces.
  • swift test --filter XCSkipTests passes, including testLocalizedPluralStringFormatting from Added string pluralization support #102.

Skip Pull Request Checklist:

  • REQUIRED: I have signed the Contributor Agreement
  • REQUIRED: I have tested my change locally with swift test
  • OPTIONAL: I have tested my change on an iOS simulator or device
  • OPTIONAL: I have tested my change on an Android emulator or device

  • AI was used to generate or assist with generating this PR. Claude Code wrote the change and the test. The test was run on Robolectric, and confirmed to fail with the change reverted.

🤖 Generated with Claude Code

…guments

The .stringsdict to ICU MessageFormat conversion only mapped positional %N$@
and %N$d to {N-1}, so Xcode's default plural form "%lld files" rendered as
"%lld files", and literal braces, apostrophes or # broke the pattern. Every
specifier now becomes an argument: integer conversions as {n,number,#},
which formats without grouping like Darwin, the rest as {n}. %% becomes %,
and ICU syntax characters in the literal text are quoted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label Oct 8, 2026
piercifani added a commit to piercifani/skip-android-bridge that referenced this pull request Oct 8, 2026
…rmat

Replace the bridge's own ICU pattern parser and PluralCategoryAccess with a
call to String.localizedStringWithFormat, so native String(localized:) and
transpiled code share one plural path. Requires skiptools/skip-foundation#134
for Xcode's default non-positional specifiers (%lld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
piercifani added a commit to piercifani/skip-android-bridge that referenced this pull request Oct 8, 2026
…rmat

Replace the bridge's own ICU pattern parser and PluralCategoryAccess with a
call to String.localizedStringWithFormat, so native String(localized:) and
transpiled code share one plural path. Pin skip-foundation to
skiptools/skip-foundation#134, which this needs for Xcode's default
non-positional specifiers (%lld), until it ships in a release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@marcprux marcprux left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice fix, thanks! Feel free to merge.

@piercifani
piercifani merged commit c5d74ef into skiptools:main Oct 8, 2026
2 checks passed
@piercifani
piercifani deleted the plural-printf-specifiers branch October 8, 2026 15:22
piercifani added a commit to piercifani/skip-android-bridge that referenced this pull request Oct 9, 2026
…rmat

Replace the bridge's own ICU pattern parser and PluralCategoryAccess with a
call to String.localizedStringWithFormat, so native String(localized:) and
transpiled code share one plural path. Requires skip-foundation 1.4.7
(skiptools/skip-foundation#134) for Xcode's default non-positional
specifiers (%lld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants