A multilingual video-call, collaboration, or screen-recording app can look finished in the lobby, mute row, and chat thread, then break the moment someone starts a screen share. The system broadcast picker still lists an English Broadcast Upload Extension name. ReplayKit setup or failure copy resolves from an unlocalized extension bundle. Android posts an English mediaProjection foreground notification for the entire capture session. Translators already finished the host UI. Screen-share chrome still drifted because those strings live in a Broadcast Upload Extension target and a typed capture service, not in the ordinary SwiftUI or Activity catalog.
Give screen share the same release discipline as the call UI. Inventory Broadcast Upload and MediaProjection strings apart from host screens, localize picker-facing names and capture notifications on purpose, then prove both surfaces after real Language and Region changes.
Why screen-share chrome drifts from the app language
Screen capture has two owners. The host app owns the button that starts sharing. The system owns the consent or picker chrome, while your Broadcast Upload Extension and Android capture service own the labels users keep seeing while the share is active. Apple's ReplayKit framework describes recording or streaming screen and audio content and building app extensions for live broadcasting. RPBroadcastSampleHandler is the principal class that processes ReplayKit sample buffers inside a Broadcast Upload Extension configured with RPBroadcastProcessModeSampleBuffer in the extension Info.plist. RPSystemBroadcastPickerView adds an in-app broadcast button that presents a broadcast picker and can limit selection with preferredExtension set to a broadcast extension bundle identifier.
Android requires a parallel split. Foreground service types document the mediaProjection type, the FOREGROUND_SERVICE_MEDIA_PROJECTION permission, and the rule that apps must call createScreenCaptureIntent() before starting the typed foreground service. AOSP MediaProjectionManager spells out the same flow: declare the service type, obtain the screen-capture intent result, start the foreground service with FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION, then call getMediaProjection. AOSP MediaProjection describes the token that grants capture of screen contents. The consent UI is system-owned. The persistent notification title is app-owned.
The localization gap shows up in six places:
- Host call UI strings live in the main String Catalog or
strings.xml. The Broadcast UploadCFBundleDisplayNameoften stays an English Info.plist literal such as "Broadcast Extension". RPSystemBroadcastPickerView.preferredExtensionselects which extension appears. It does not translate the label users read in the picker.RPBroadcastSampleHandlerlifecycle methods run in a separate process. User-facing failure or status strings authored there resolve from the extension bundle, not the host catalog.- Android capture continues only while a
mediaProjectionforeground service stays alive. Teams localize Activities and leave EnglishNotificationtitles and channel names inside the service. - System Screen Broadcast or MediaProjection consent dialogs are not your string table. Shipping a translated Start button does not prove the extension name or capture notification is localized.
- On iOS 27+, LiveKit's screen-sharing guide documents ScreenCaptureKit capture that can retire the Broadcast Upload Extension. Android still needs app-owned notification chrome, so a platform matrix that only tested the old extension path can miss one side.
Open-source product configuration matches that split. LiveKit's example BroadcastExt Info.plist sets CFBundleDisplayName to "LiveKit Broadcast Example". Jitsi Meet's broadcast-extension Info.plist uses "Jitsi Meet Broadcast Extension". Those values are picker-facing product strings sitting in the extension target. A Stack Overflow thread on whether app extensions need their own Localizable.strings confirms extension targets resolve localization from bundles that must include the strings file. Another thread on MediaProjection foreground-service type requirements shows practitioners wiring capture through a dedicated service that must call startForeground, the same process that owns the persistent notification. Phrase's iOS localization overview and the live i18nAgent iOS and Android guides cover catalogs and UI strings. They do not connect Broadcast Upload display names to MediaProjection notification ownership. Share-sheet targets, VPN tunnel status, Control Center tiles, and scheduled local notifications covered elsewhere are different problems: a translated share extension or VPN Settings name does not localize screen-broadcast picker identity or capture-service chrome.
Localize the Broadcast Upload picker name on purpose
Localize the Broadcast Upload Extension CFBundleDisplayName in the extension target InfoPlist.strings: RPSystemBroadcastPickerView preferredExtension selects the extension, but the picker-facing label comes from that separate Info.plist field, not the host app display name.
Treat the extension display name as a first-class localization key:
| Field | Why it matters |
|---|---|
| Stable key | Shared meaning for "screen broadcast" / "share screen" across marketing and picker copy |
| Apple surface | Extension CFBundleDisplayName via InfoPlist.strings; optional SampleHandler status strings |
| Android surface | mediaProjection notification title, text, and channel name/description |
| Bundle owner | Broadcast Upload Extension target for Info.plist strings; capture service module for Android resources |
| Display path | RPSystemBroadcastPickerView, Control Center broadcast list, Android ongoing capture notification |
| Fallback | Base localization that remains English-complete without masking missing extension translations in QA |
| Verification | Device Language and Region, per-app language, fresh install, picker open, capture start, notification shade |
Put a localization identifier in the extension Info.plist CFBundleDisplayName, then translate that identifier in each locale's extension-target InfoPlist.strings. Keep the file in the Broadcast Upload target membership. Do not assume the host app's localized display name appears in the broadcast picker when preferredExtension points at a child extension. Give translators a short length budget: picker rows truncate long German compound names.
Wire preferredExtension to the real extension bundle identifier before presentation, as Apple's picker documentation shows. A wrong bundle id can hide your localized extension and surface another English provider name during QA.
Keep SampleHandler strings inside the extension catalog
Keep RPBroadcastSampleHandler user-facing status or failure strings in the Broadcast Upload Extension String Catalog or Localizable.strings; host-app catalogs do not automatically ship inside com.apple.broadcast-services-upload.
RPBroadcastSampleHandler runs broadcastStarted(withSetupInfo:), broadcastPaused(), broadcastResumed(), and broadcastFinished() in the extension process. If you surface errors, local notifications to the host, or any user-readable setup copy from those callbacks, resolve them with the extension's own String Catalog or Localizable.strings. Export for Localization must include the extension target. A host-only XLIFF pass can leave the upload extension English even when the call UI is complete.
LiveKit's guide stresses that Broadcast Capture uses a separate process plus an app group. Use that architecture reminder in localization planning: separate process means separate bundle resources. When you adopt ScreenCaptureKit on iOS 27+ and retire the extension, remove or gate extension-only keys so translators are not maintaining dead picker names while Android notification strings remain mandatory.
Own MediaProjection notification chrome in the service process
Resolve mediaProjection foreground notification titles, channel names, and capture-error copy with getString inside the typed foreground service, and re-check the ScreenCaptureKit path on iOS 27+ where the Broadcast Upload Extension may disappear while Android notification chrome remains app-owned.
Android's foreground-service types page requires the mediaProjection type and a successful createScreenCaptureIntent() permission step before the service may run. After that step, your app still builds the notification passed to startForeground. Load title, text, and channel labels with getString (or your localization layer) inside the service process. Create notification channels with localized names at runtime for the active locale. Do not hardcode "Screen recording" or "Sharing screen" in Kotlin or Java.
Document the split for QA:
- System consent / picker chrome: verify it appears, but do not file localization bugs against OS strings you do not own.
- Extension
CFBundleDisplayNameand SampleHandler copy: app-owned on Broadcast Upload paths. - Android ongoing notification and channel labels: app-owned for every MediaProjection session.
- Host in-call "You are sharing" banners: host catalog, still required, still insufficient alone.
When the same product ships both platforms, share semantic keys for "sharing your screen" across iOS picker names and Android notification titles even if the exact wording differs for chrome constraints. Re-verify after per-app language changes on both platforms: an extension or service that cached an eagerly resolved English String will keep showing it until restart.
Verify after device and per-app language changes
A translated host screen and a green ReplayKit integration test are not enough. Run a short language matrix before release:
- Cold start in German or Japanese. Open
RPSystemBroadcastPickerViewor the system broadcast UI and confirm the Broadcast Upload display name matches the locale. - Start capture on Android and open the notification shade. Confirm title, text, and channel name are localized.
- Switch the per-app language while the host UI updates. Restart capture and confirm extension and notification strings follow the new language.
- On iOS 27 test devices, exercise both ScreenCaptureKit and any residual Broadcast Upload path your SDK still exposes so one platform matrix does not hide the other.
- Fresh install with only the base localization, then with a partial locale, to catch English fallback that masks missing extension
InfoPlist.stringsentries.
Block the release if the host call UI is localized but the picker still shows "Broadcast Extension" or the capture notification still says "Screen recording" in English.
What not to confuse with this workflow
Do not treat this as a share-sheet problem. Share extensions and share targets belong to a different inventory. Do not treat it as VPN Packet Tunnel or VpnService status chrome, Control Center toggles, marketing push copy, or scheduled local notification rescheduling. Those surfaces can be localized while Broadcast Upload picker names and MediaProjection notifications stay English. Keep screen-share capture identity on its own checklist beside the host call UI.
