Skip to content

Add String(localized:) and String.LocalizationValue for Android - #38

Merged
marcprux merged 2 commits into
skiptools:mainfrom
piercifani:string-localized
Oct 5, 2026
Merged

marcprux merged 2 commits into
skiptools:mainfrom
piercifani:string-localized

Conversation

@piercifani

@piercifani piercifani commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes skiptools/skip#538.

Native modules on Android have no String(localized:): swift-foundation on non-Darwin platforms doesn't ship String.LocalizationValue or the String(localized:) initializers. Today every module that wants them adds its own NSLocalizedString shim, and it has to be per-module because it calls the module-internal let NSLocalizedString = AndroidLocalizedString() generated in Bundle_Support.swift. String interpolation inside String(localized:) also can't work through such a shim: the shim receives the already-interpolated string as the lookup key.

This adds the missing API to SkipAndroidBridge, built on the existing AndroidStringInterpolation and AndroidLocalizedString:

  • String.LocalizationValue (AndroidLocalizationValue)
  • String(localized:table:bundle:locale:comment:)
  • String(localized:defaultValue:table:bundle:locale:comment:)
  • String(localized: LocalizedStringResource)

Notes:

  • bundle: is typed AndroidBundle?, so bundle: .module resolves the module's synthesized Bundle.module (declared on AndroidBundle via the generated Bundle typealias).
  • As on Darwin, the lookup key escapes a literal % as %% only when the string is interpolated ("100% done" → "100% done", "\(n)% done" → "%lld%% done"). Verified against Darwin Foundation.
  • Compiled on every non-Darwin platform. On Android the lookup goes through AndroidBundle. On other hosts, such as the linux-gnu host pass of skip export, it uses Foundation.Bundle. Darwin, including a Robolectric host on macOS, keeps Foundation's implementation.
  • Plural variations from .xcstrings are not handled, matching the transpiled String(localized:) in skip-foundation.

Testing

  • New testStringLocalized sample test with an en.lproj/Localizable.strings (a .xcstrings is copied uncompiled by the swift.org toolchain with Xcode 16.4): literal keys, keys containing %, interpolation with positional arguments, untranslated fallback, defaultValue:, dynamic keys. It passes on macOS, where Foundation's implementation produces the same expected values. It is skipped on Robolectric on a macOS host, which runs Foundation's implementation and doesn't read the AndroidBundle strings.
  • swift test (macOS + Robolectric) green; skip android build green.
  • On-device: built a large consumer app (Polymarket, 9 native Skip modules) against this branch with its per-module shims deleted. All 8000 of its native tests pass on an arm64 emulator, including assertions on localized format output that now go through this API.

🤖 Generated with Claude Code

Implements the missing Foundation String(localized:table:bundle:locale:comment:),
String(localized:defaultValue:...) and String(localized: LocalizedStringResource)
on Android on top of AndroidStringInterpolation and AndroidLocalizedString, so
native modules no longer need a per-module NSLocalizedString shim
(skiptools/skip#538).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label Oct 4, 2026
…ple table

The linux-gnu host pass of skip export compiles native modules against Foundation
that also lacks String(localized:), so provide it on every non-Darwin platform,
backed by Foundation.Bundle outside Android. The sample strings move from
.xcstrings to en.lproj/Localizable.strings because the swift.org toolchain with
Xcode 16.4 copies .xcstrings without compiling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@marcprux
marcprux merged commit 721cc58 into skiptools:main Oct 5, 2026
3 checks passed
@piercifani
piercifani deleted the string-localized branch October 5, 2026 20:41
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.

Bridge String(localized:bundle:)

2 participants