#1818

Keep channel invite links intact in every language

OpenRodsF1wants to merge intofrom

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, because MessageListView.handleOpenURL rejects an empty geohash. In right-to-left text the link is also reordered: drawn on one line, it reads u4pru/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: .current put 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

Activity

2
chatgpt-codex-connector[bot]5d ago

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review⚠️ Failed 2026-10-25T23:00:35.680959Z
We couldn’t complete this review. Comment @codex review to try again.
e14a4ccPR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.