Summary
Channel invites (#1513) lose a link to NSDataDetector in five languages:
- Arabic, Persian, Hebrew, Urdu: NSDataDetector finds the deep link only as
bitchat://geohash, with no geohash. Tapping that opens bitchat without opening the channel, becauseMessageListView.handleOpenURLrejects an empty geohash. In right-to-left text the link is also reordered: drawn on one line, it readsu4pru/bitchat://geohash. - Korean: NSDataDetector reads the App Store link as
…/id6748219622에서, which apps.apple.com answers with a 404 in a desktop or Android browser.
Cause
ChannelShare.payload formatted with locale: .current. Given any locale, Foundation wraps every argument of a right-to-left format in bidi isolates (U+2068…U+2069), and NSDataDetector stops at the first one. The format's text decides it, not the locale: the Persian format gets isolates even with en_US, and the English format gets none with fa_IR. With locale: nil there are none.
Korean's translation attaches the particle directly to the URL (%2$@에서), so NSDataDetector reads it as part of the URL, with or without a locale.
Fix
- Format the payload with
locale: nil. Both arguments are plain strings, so the locale contributed nothing but the isolates. - Korean: one space,
%2$@에서→%2$@ 에서. The rest of the translation is unchanged. No new keys and no other locale changes.
In Swift the only behavior change is locale: nil; payload(forGeohash:) keeps its signature and its callers are untouched. Lookup and formatting are split into payload(forGeohash:format:) so the test can run every locale's format through the code that ships. CI runs in English, so the public function alone would only ever format the English string.
The other locale: .current calls build text shown inside bitchat. The invite is the one formatted string that leaves through the share sheet.
Test
everyLocaleKeepsBothInviteLinksIntact reads every locale's channel.share.payload from the catalog (the same way LocalizationCoverageTests does), formats it with payload(forGeohash:format:), and asserts that NSDataDetector finds the full deep link and the App Store URL.
- With
locale: .currentput back and main's Korean string, it fails for ar, fa, he, ur (deep link) and ko (store link). Reverting only one half fails only that half's locales. - Locally, with xcodebuild, the full suites pass: macOS (2199 Swift Testing + 214 XCTest) and the iOS 26.5 simulator (2202 + 214). The new test also passes on the iOS 17.4 simulator.
- The isolates and NSDataDetector's results are the same on macOS 27, the iOS 17.4 simulator, and the iOS 26.5 simulator.
🤖 Generated with Claude Code
Codex Review Summary
This comment shows the latest Codex review activity on this pull request.
We couldn’t complete this review. Comment
@codex reviewto try again.e14a4ccℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.