Skip to content

feat(i18n)!: adopt the shared @stream-io/i18n runtime - #2436

Merged
oliverlaz merged 5 commits into
mainfrom
feat/i18n-shared-runtime
Sep 11, 2026
Merged

oliverlaz merged 5 commits into
mainfrom
feat/i18n-shared-runtime

Conversation

@oliverlaz

@oliverlaz oliverlaz commented Sep 11, 2026

Copy link
Copy Markdown
Member

💡 Overview

Adopts @stream-io/i18n — the translation runtime we extracted from Chat — across both video SDKs and both dogfood apps, and moves from English-sentence translation keys to namespaced dotted identifiers carrying their English copy inline. Same mechanism as stream-chat-react#3261.

t('Mute all'); // before
t('participantList.muteAll.label', 'Mute all'); // after

The catalog is generated from the t() call sites themselves, so there is no en.json in either SDK any more, and drift between code and catalog is structurally impossible rather than merely discouraged.

Rendered English does not change. A mechanical diff of all 192 mapped rows reports zero unexpected copy changes — the only differences are four deliberate variable renames, listed below.

Important

Depends on @stream-io/i18n@^1.0.1, now published. The lockfile resolves it from npm and yarn install --immutable passes locally, so CI should be green.

📝 Implementation notes

What moved where

  • packages/react-bindings sheds i18n entirelyStreamI18n, StreamI18nContext and the i18next dependency are gone. It could not keep them: the key catalog is generated per-SDK from that SDK's own call sites, so it lives downstream of bindings. StreamVideoProvider is now client-only and its props type is renamed StreamVideoProviderProps.
  • Each SDK owns src/i18n/ — a thin Streami18n subclass, runtimeDefaults.ts, the generated keys.ts, the derived types, and the React context built on core's createTranslationContext factory.
  • Keys shared between web and RN use identical strings (common.live.label, participantView.screenShare.stop.label), so a customer shipping both platforms writes one dictionary.
  • runtimeDefaults is empty in both SDKs. The four lookup tables that resolved a key from a runtime value are now switch statements of literal t() calls, which is what makes them translatable at all.

Three bugs fixed on the way

  1. translationsOverrides replaced instead of merged. <StreamVideoProvider translationsOverrides={translations} {...props} /> spread props last, so an integrator passing one German string silently dropped all 147 SDK strings. Invisible in English (the old parseMissingKeyHandler echoed the key), total in every other language. Both dogfood apps carried a ...sdkTranslations.en re-merge to work around it — now deleted.
  2. fallbackLanguage was accepted and dropped on the floor. StreamVideoProvider never forwarded it. Removed rather than wired up: the fallback is now each call site's inline English, which cannot be forgotten. react-dogfood also seeded language from navigator.language, so de-DE never matched the bare de dictionary — now normalized and clamped.
  3. **t(\Sign in with ${provider.name}`)** in react-dogfood` was a template literal matching no key in any dictionary. It has always echoed.

Newly translatable

  • 51 keys were used in code but absent from every catalog (most of CallStats, plus Cancel, More options). They rendered only because the missing-key handler echoed them, so they were untranslatable in every language.
  • 11 more — the eight CallingState labels and three call-stats verdicts — resolved from a runtime value with no catalog entry at all.

Deliberate copy changes (the only four)

Key Change
participantList.anonymousCount.text {{ count }}{{ anonymousCount }} — the entry has no plural forms, so i18next's magic count was searching for forms that do not exist
livestream.backstage.participantsJoinedEarly.text adds {{ formattedCount }}; count stays numeric so plural selection works when humanize() is on. A count of 1 now renders the singular
lobby.footer.otherParticipants.text (RN) two hand-branched sentences merged into one plural key; {{numberOfParticipants}}{{ count }}
livestreamViewer.earlyParticipants.text (RN) count moved inside t(); 1 now renders "1 participant has joined early" instead of "1 participants…"

{{ direction }} fullscreen / picture-in-picture were composed from two lookups (t('Leave') / t('Enter') filling a slot) — unlocalizable, since word order is fixed. They are now four whole sentences.

Also here

  • react-sdk gets a vitest setup (matching client and codemod) for the catalog render test. The three colocated test files that existed but had never been run by any script are ported from node:test to vitest and now run in CI — which surfaced a real bug: their fixtures lacked publishedTracks, and the filter-object path reads it unguarded, so two cases threw.
  • CI drift gatebuild-translations regenerates keys.ts and the render fixture; a diff fails the build.
  • ai-docs/i18n-2.0-key-map.json (197 rows, both platforms) and ai-docs/i18n-2.0-migration.md for integrators.

✅ Verification

  • Rendered-English diff across 192 rows: 0 unexpected changes
  • tsc: react-sdk 0, react-native-sdk 0, both dogfoods 0
  • eslint --max-warnings=0 and prettier --check: clean across packages/** and sample-apps/**
  • Catalog render tests: 5/5 web, 5/5 RN — including guarantee G2 (a partial dictionary renders English, never a dotted path)
  • react-sdk vitest: 32/32 across 4 files · RN jest: 11/11 suites, 38/38
  • Codegen guards pass; catalogs byte-stable across regeneration
  • react-dogfood exercised in the browser in English, German and Spanish — lobby, settings, transcriptions, in-call, participant menu and call stats. A DOM sweep for raw dotted keys and uninterpolated {{ placeholders }} found none on any screen

🔜 Before merge

Merge the companion docs PR alongside this one.

.yarnrc.yml gains an @stream-io/i18n entry under npmPreapprovedPackagesnpmMinimalAgeGate: 2d otherwise blocks a version published today.

🎫 Ticket: https://linear.app/stream/issue/REACT-1088/replace-natural-language-i18n-keys-with-namespaced-type-checked-ids

📑 Docs: https://github.com/GetStream/docs-content/pull/1592

Replaces the hand-rolled i18next wrapper in `react-bindings` with
`@stream-io/i18n`, the translation runtime shared with Stream Chat, and
moves both SDKs from English-sentence translation keys to namespaced
dotted identifiers that carry their English copy inline.

Keys are now `t('participantList.muteAll.label', 'Mute all')`. The
catalog is generated from the call sites themselves, so `en.json` is
gone from both SDKs and drift between code and catalog is structurally
impossible rather than merely discouraged.

Rendered English is unchanged. A mechanical diff of all 192 mapped rows
reports zero unexpected copy changes; the only differences are four
deliberate variable renames.

What this fixes beyond the rename:

- `translationsOverrides` replaced the SDK's dictionary instead of
  merging, so an integrator supplying one string dropped all 147 SDK
  strings. The prop is now `translations` and registers over the
  bundled defaults; partial dictionaries are safe.
- `fallbackLanguage` was accepted and silently dropped on the floor.
  Removed - the fallback is each call site's inline English.
- 51 keys were used in code but absent from every catalog, so they were
  untranslatable in every language. They are real keys now.
- 11 more strings (the `CallingState` labels and call-stats verdicts)
  became translatable for the first time; they used to resolve from a
  runtime value with no catalog entry.

BREAKING CHANGE: `StreamI18n`, `StreamI18nProvider`, `translations`,
`translationsOverrides`, `fallbackLanguage`, `TranslationLanguage`,
`TranslationsMap` and `TranslatorFunction` are removed from both SDKs.
Custom dictionaries must be re-keyed - an old key never matches, so the
override silently stops applying. See `ai-docs/i18n-2.0-migration.md`
and `ai-docs/i18n-2.0-key-map.json`.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 201 files, which is 101 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 086c9250-327c-4a3e-b3bc-3225865d4f5d

📥 Commits

Reviewing files that changed from the base of the PR and between ceeddba and f14721f.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (201)
  • .github/workflows/test.yml
  • .yarnrc.yml
  • ai-docs/i18n-2.0-key-map.json
  • ai-docs/i18n-2.0-migration.md
  • packages/react-bindings/index.ts
  • packages/react-bindings/package.json
  • packages/react-bindings/src/contexts/StreamI18nContext.tsx
  • packages/react-bindings/src/contexts/StreamVideoContext.tsx
  • packages/react-bindings/src/contexts/index.ts
  • packages/react-bindings/src/i18n/StreamI18n.ts
  • packages/react-bindings/src/i18n/index.ts
  • packages/react-bindings/src/i18n/types.ts
  • packages/react-native-sdk/__tests__/i18n/catalog.fixture.json
  • packages/react-native-sdk/__tests__/i18n/catalogRenders.test.ts
  • packages/react-native-sdk/package.json
  • packages/react-native-sdk/scripts/generate-i18n-keys.mts
  • packages/react-native-sdk/src/components/Call/Lobby/JoinCallButton.tsx
  • packages/react-native-sdk/src/components/Call/Lobby/Lobby.tsx
  • packages/react-native-sdk/src/components/Call/Lobby/LobbyFooter.tsx
  • packages/react-native-sdk/src/components/Call/RingingCallContent/CallLeftIndicator.tsx
  • packages/react-native-sdk/src/components/Call/RingingCallContent/CallPreparingIndicator.tsx
  • packages/react-native-sdk/src/components/Call/RingingCallContent/IncomingCall.tsx
  • packages/react-native-sdk/src/components/Call/RingingCallContent/OutgoingCall.tsx
  • packages/react-native-sdk/src/components/Livestream/LivestreamControls/HostStartStreamButton.tsx
  • packages/react-native-sdk/src/components/Livestream/LivestreamPlayer/LivestreamEnded.tsx
  • packages/react-native-sdk/src/components/Livestream/LivestreamTopView/LiveIndicator.tsx
  • packages/react-native-sdk/src/components/Livestream/ViewerLivestream/ViewerLobby.tsx
  • packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantLabel.tsx
  • packages/react-native-sdk/src/components/utility/ScreenShareOverlay.tsx
  • packages/react-native-sdk/src/i18n/Streami18n.ts
  • packages/react-native-sdk/src/i18n/TranslationContext.tsx
  • packages/react-native-sdk/src/i18n/index.ts
  • packages/react-native-sdk/src/i18n/keys.ts
  • packages/react-native-sdk/src/i18n/runtimeDefaults.ts
  • packages/react-native-sdk/src/i18n/types.ts
  • packages/react-native-sdk/src/i18n/useStreami18n.ts
  • packages/react-native-sdk/src/index.ts
  • packages/react-native-sdk/src/providers/StreamVideo.tsx
  • packages/react-native-sdk/src/translations/en.json
  • packages/react-native-sdk/src/translations/index.ts
  • packages/react-sdk/CLAUDE.md
  • packages/react-sdk/index.ts
  • packages/react-sdk/package.json
  • packages/react-sdk/rollup.config.mjs
  • packages/react-sdk/scripts/generate-i18n-keys.mts
  • packages/react-sdk/src/components/Button/CompositeButton.tsx
  • packages/react-sdk/src/components/CallControls/CallStatsButton.tsx
  • packages/react-sdk/src/components/CallControls/CancelCallButton.tsx
  • packages/react-sdk/src/components/CallControls/ReactionsButton.tsx
  • packages/react-sdk/src/components/CallControls/RecordCallButton.tsx
  • packages/react-sdk/src/components/CallControls/ScreenShareButton.tsx
  • packages/react-sdk/src/components/CallControls/ToggleAudioButton.tsx
  • packages/react-sdk/src/components/CallControls/ToggleAudioOutputButton.tsx
  • packages/react-sdk/src/components/CallControls/ToggleVideoButton.tsx
  • packages/react-sdk/src/components/CallParticipantsList/CallParticipantListHeader.tsx
  • packages/react-sdk/src/components/CallParticipantsList/CallParticipantListingItem.tsx
  • packages/react-sdk/src/components/CallParticipantsList/CallParticipantsList.tsx
  • packages/react-sdk/src/components/CallParticipantsList/EmptyParticipantSearchList.tsx
  • packages/react-sdk/src/components/CallRecordingList/CallRecordingListHeader.tsx
  • packages/react-sdk/src/components/CallStats/CallStats.tsx
  • packages/react-sdk/src/components/DeviceSettings/DeviceSettings.tsx
  • packages/react-sdk/src/components/DeviceSettings/SpeakerTest.tsx
  • packages/react-sdk/src/components/Notification/MicCaptureErrorNotification.tsx
  • packages/react-sdk/src/components/Notification/RecordingInProgressNotification.tsx
  • packages/react-sdk/src/components/Notification/SpeakingWhileMutedNotification.tsx
  • packages/react-sdk/src/components/Permissions/PermissionRequests.tsx
  • packages/react-sdk/src/components/RingingCall/RingingCall.tsx
  • packages/react-sdk/src/components/VideoPreview/VideoPreview.tsx
  • packages/react-sdk/src/core/components/CallLayout/LivestreamLayout.tsx
  • packages/react-sdk/src/core/components/CallLayout/PaginatedGridLayout.tsx
  • packages/react-sdk/src/core/components/CallLayout/PipLayout/Grid.tsx
  • packages/react-sdk/src/core/components/CallLayout/PipLayout/Pip.tsx
  • packages/react-sdk/src/core/components/CallLayout/SpeakerLayout.tsx
  • packages/react-sdk/src/core/components/CallLayout/partcipantFilter.test.ts
  • packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx
  • packages/react-sdk/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx
  • packages/react-sdk/src/core/components/StreamVideo/StreamVideo.tsx
  • packages/react-sdk/src/core/components/Video/DefaultPictureInPicturePlaceholder.tsx
  • packages/react-sdk/src/core/components/Video/DefaultVideoPlaceholder.tsx
  • packages/react-sdk/src/embedded/call/CallControls.tsx
  • packages/react-sdk/src/embedded/livestream/host/HostLayout.tsx
  • packages/react-sdk/src/embedded/livestream/host/HostStateRouter.tsx
  • packages/react-sdk/src/embedded/livestream/viewer/ViewerLayout.tsx
  • packages/react-sdk/src/embedded/livestream/viewer/ViewerLobby.tsx
  • packages/react-sdk/src/embedded/shared/BlurToggleButton/BlurToggleButton.tsx
  • packages/react-sdk/src/embedded/shared/CallFeedback/CallEndedScreen.tsx
  • packages/react-sdk/src/embedded/shared/CallFeedback/RatingScreen.tsx
  • packages/react-sdk/src/embedded/shared/CallFeedback/StarRating.tsx
  • packages/react-sdk/src/embedded/shared/CallFeedback/ThankYouScreen.tsx
  • packages/react-sdk/src/embedded/shared/ConnectionNotification/ConnectionNotification.tsx
  • packages/react-sdk/src/embedded/shared/JoinError/JoinError.tsx
  • packages/react-sdk/src/embedded/shared/Lobby/DeviceControls.tsx
  • packages/react-sdk/src/embedded/shared/Lobby/Lobby.tsx
  • packages/react-sdk/src/embedded/shared/Lobby/ToggleMicButton.tsx
  • packages/react-sdk/src/embedded/shared/Lobby/VideoPreviewFallbacks.tsx
  • packages/react-sdk/src/hooks/useDeviceList.tsx
  • packages/react-sdk/src/i18n/Streami18n.ts
  • packages/react-sdk/src/i18n/TranslationContext.tsx
  • packages/react-sdk/src/i18n/__tests__/catalog.fixture.json
  • packages/react-sdk/src/i18n/__tests__/catalogRenders.test.ts
  • packages/react-sdk/src/i18n/index.ts
  • packages/react-sdk/src/i18n/keys.ts
  • packages/react-sdk/src/i18n/runtimeDefaults.ts
  • packages/react-sdk/src/i18n/types.ts
  • packages/react-sdk/src/i18n/useStreami18n.ts
  • packages/react-sdk/src/translations/en.json
  • packages/react-sdk/src/translations/index.ts
  • packages/react-sdk/src/utilities/filter.test.ts
  • packages/react-sdk/src/utilities/normalizeString.test.ts
  • packages/react-sdk/vite.config.mts
  • sample-apps/react-native/dogfood/CLAUDE.md
  • sample-apps/react-native/dogfood/src/components/AudioRoom/DescriptionPanel.tsx
  • sample-apps/react-native/dogfood/src/components/AudioRoom/LiveButtons.tsx
  • sample-apps/react-native/dogfood/src/components/AudioRoom/PermissionRequestsPanel.tsx
  • sample-apps/react-native/dogfood/src/components/CallErrorComponent.tsx
  • sample-apps/react-native/dogfood/src/components/CallStats.tsx
  • sample-apps/react-native/dogfood/src/components/LobbyViewComponent.tsx
  • sample-apps/react-native/dogfood/src/components/MeetingUI.tsx
  • sample-apps/react-native/dogfood/src/components/NavigationHeader.tsx
  • sample-apps/react-native/dogfood/src/components/ParticipantActions.tsx
  • sample-apps/react-native/dogfood/src/components/ParticipantsInfoListModal.tsx
  • sample-apps/react-native/dogfood/src/components/VideoWrapper.tsx
  • sample-apps/react-native/dogfood/src/hooks/useAppI18n.ts
  • sample-apps/react-native/dogfood/src/screens/AudioRoom/CreateRoomModal.tsx
  • sample-apps/react-native/dogfood/src/screens/AudioRoom/RoomList.tsx
  • sample-apps/react-native/dogfood/src/screens/Call/JoinCallScreen.tsx
  • sample-apps/react-native/dogfood/src/screens/ChooseAppModeScreen.tsx
  • sample-apps/react-native/dogfood/src/screens/LiveStream/JoinLiveStream.tsx
  • sample-apps/react-native/dogfood/src/screens/LiveStream/QRScanner.tsx
  • sample-apps/react-native/dogfood/src/screens/LiveStream/index.tsx
  • sample-apps/react-native/dogfood/src/screens/LoginScreen/index.tsx
  • sample-apps/react-native/dogfood/src/screens/Meeting/GuestModeScreen.tsx
  • sample-apps/react-native/dogfood/src/screens/Meeting/JoinMeetingScreen.tsx
  • sample-apps/react-native/dogfood/src/translations/en.json
  • sample-apps/react-native/dogfood/src/translations/index.ts
  • sample-apps/react/react-dogfood/CLAUDE.md
  • sample-apps/react/react-dogfood/components/ActiveCall.tsx
  • sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx
  • sample-apps/react/react-dogfood/components/AdaptivePipGrid.tsx
  • sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingSearchForm.tsx
  • sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingsPage.tsx
  • sample-apps/react/react-dogfood/components/CallStatsWrapper.tsx
  • sample-apps/react/react-dogfood/components/ChatUI.tsx
  • sample-apps/react/react-dogfood/components/ChatWrapper.tsx
  • sample-apps/react/react-dogfood/components/ClosedCaptions.tsx
  • sample-apps/react/react-dogfood/components/Debug/DebugParticipantViewUI.tsx
  • sample-apps/react/react-dogfood/components/DefaultAppHeader.tsx
  • sample-apps/react/react-dogfood/components/DegradedPerformanceNotification.tsx
  • sample-apps/react/react-dogfood/components/DeviceDisconnectedNotification.tsx
  • sample-apps/react/react-dogfood/components/E2EEKeyNotification.tsx
  • sample-apps/react/react-dogfood/components/Feedback/Feedback.tsx
  • sample-apps/react/react-dogfood/components/IncomingVideoSettings.tsx
  • sample-apps/react/react-dogfood/components/InviteLinkButton.tsx
  • sample-apps/react/react-dogfood/components/InvitePanel/InvitePanel.tsx
  • sample-apps/react/react-dogfood/components/LayoutSelector.tsx
  • sample-apps/react/react-dogfood/components/Lobby.tsx
  • sample-apps/react/react-dogfood/components/LobbyEncryption.tsx
  • sample-apps/react/react-dogfood/components/MeetingUI.tsx
  • sample-apps/react/react-dogfood/components/MobileAppBanner.tsx
  • sample-apps/react/react-dogfood/components/Ringing/DialerPage.tsx
  • sample-apps/react/react-dogfood/components/Ringing/DialingCallNotification.tsx
  • sample-apps/react/react-dogfood/components/Ringing/RingingCallNotification.tsx
  • sample-apps/react/react-dogfood/components/Settings/DeviceSelection.tsx
  • sample-apps/react/react-dogfood/components/Settings/LanguageMenu.tsx
  • sample-apps/react/react-dogfood/components/Settings/SettingsTabModal.tsx
  • sample-apps/react/react-dogfood/components/Settings/Transcriptions.tsx
  • sample-apps/react/react-dogfood/components/Settings/VideoEffects.tsx
  • sample-apps/react/react-dogfood/components/ToggleCameraButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleDeveloperButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleDocumentationButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleDualCameraButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleDualMicButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleEffectsButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleFeedbackButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleHiFiButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleLayoutButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleMicButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleMoreOptionsListButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleNoiseCancellationButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleParticipantListButton.tsx
  • sample-apps/react/react-dogfood/components/ToggleParticipantsPreview.tsx
  • sample-apps/react/react-dogfood/components/TourPanel/TourPanel.tsx
  • sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx
  • sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx
  • sample-apps/react/react-dogfood/context/SettingsContext.tsx
  • sample-apps/react/react-dogfood/hooks/useAppI18n.ts
  • sample-apps/react/react-dogfood/hooks/useLanguage.ts
  • sample-apps/react/react-dogfood/hooks/useLayoutSwitcher.ts
  • sample-apps/react/react-dogfood/i18n/runtimeDefaults.ts
  • sample-apps/react/react-dogfood/pages/auth/signin.tsx
  • sample-apps/react/react-dogfood/pages/bare/join/[callId].tsx
  • sample-apps/react/react-dogfood/pages/index.tsx
  • sample-apps/react/react-dogfood/pages/join/[callId].tsx
  • sample-apps/react/react-dogfood/translations/app/de.json
  • sample-apps/react/react-dogfood/translations/app/es.json
  • sample-apps/react/react-dogfood/translations/de.json
  • sample-apps/react/react-dogfood/translations/en.json
  • sample-apps/react/react-dogfood/translations/es.json
  • sample-apps/react/react-dogfood/translations/index.ts
  • sample-apps/react/react-dogfood/translations/sdk/de.json
  • sample-apps/react/react-dogfood/translations/sdk/es.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Replaces the local portal link used during development. The lockfile now
resolves 1.0.1 from npm, so `yarn install --immutable` passes.

`.yarnrc.yml` already allowlists the package under npmPreapprovedPackages:
`npmMinimalAgeGate: 2d` would otherwise block a version published today.
@oliverlaz
oliverlaz marked this pull request as ready for review September 11, 2026 12:36
Type-only release: dist/esm and dist/cjs are byte-identical to 1.0.1.
It narrows `TypeScriptModule` in the codegen entry point from `typeof ts`
to a `Pick<>` of the parser members it actually uses, which decouples
`scripts/generate-i18n-keys.mts` from the exact typescript version each
SDK has installed.

Catalogs regenerate byte-identically.
Resolves one conflict in VideoPreview.tsx: main added the
`createSoundDetector` import while this branch moved `useI18n` out of
`@stream-io/video-react-bindings` into the SDK's own `src/i18n` module.
Both changes are kept.

The design-system commits that landed on main touch the same components
this branch re-keyed, but only the import block collided; the `t()` call
sites auto-merged cleanly and the generated catalogs are unchanged.
@github-actions

Copy link
Copy Markdown

Bundle size

Built package output. Sizes in KB; delta vs main@ceeddba.

Package Unminified Minified Δ min vs main
@stream-io/video-react-bindings 29.6 KB 10.0 KB -1.9 KB (-16.3%)
@stream-io/video-react-sdk 395.7 KB 240.7 KB +8.0 KB (+3.5%)
↳ install total (+ client + react-bindings) 1221.4 KB 530.9 KB +6.1 KB (+1.2%)
@stream-io/video-react-sdk (embedded) (cjs) 218.4 KB 126.5 KB -67 B (-0.1%)
@stream-io/video-react-native-sdk 425.8 KB 199.3 KB +2.4 KB (+1.2%)
↳ install total (+ client + react-bindings) 1251.5 KB 489.6 KB +459 B (+0.1%)

@oliverlaz
oliverlaz merged commit 2fcd426 into main Sep 11, 2026
17 of 19 checks passed
@oliverlaz
oliverlaz deleted the feat/i18n-shared-runtime branch September 11, 2026 13:49
santhoshvai added a commit that referenced this pull request Sep 14, 2026
…2443)

### 💡 Overview

CI failed for RN sample apps as Metro only searched the app and repo
root `node_modules`, and yarn nests some deps in neither — so the bundle
failed.

Happened since #2436 on `@stream-io/state-store`. The Expo apps have
been broken longer on `react-native-url-polyfill`.

### 📝 Implementation notes

Added `packages/react-native-sdk/node_modules` as a third Metro search
path in all three sample apps. Searched last, so `react` and
`react-native` still come from the app's own folder and stay
single-copy.

🎫 Ticket: https://linear.app/stream/issue/XYZ-123

📑 Docs: https://github.com/GetStream/docs-content/pull/<id>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved dependency resolution for React Native sample apps when
bundling the SDK from source.
* Prevented duplicate React and React Native instances during Metro
builds.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants