fix(i18n)!: reject from init() and drop the Streami18n brand static - #1839
Merged
Conversation
…rror `init()` caught every failure, logged a string, and resolved. A consumer had no way to know initialization had failed short of watching the log, and an integrator who supplied a `logger` to route diagnostics somewhere quiet lost the signal entirely. Swallowing an error at a library boundary is the wrong default: the caller owns the policy. It now rejects with the original error, and core no longer logs -- so a failure is reported once, by whoever handles it, rather than twice or in a form the handler cannot inspect. The instance is still left *degraded but safe*, which is a separate guarantee and unchanged: `initialized` stays false, keeping `registerTranslation` and `setLanguage` off a dead i18next instance, and `t` remains the default translator, so every call site renders its inline English rather than a blank or a dotted key. The promise reports the failure; the store keeps the UI alive. Two changes fall out of it. The memo is cleared on failure, so a later `init()` retries rather than replaying one rejection for the lifetime of the instance -- concurrent callers still share the in-flight promise, which is the only thing the memo was ever for. And the internal bookkeeping handler attaches to a derived promise, so it does not mark the returned one as handled: the caller's `catch` still sees the rejection. BREAKING CHANGE: `Streami18n.init()` now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. Both UI SDKs catch it and report through the instance logger; a custom integration should do the same. The instance remains usable in its degraded English form either way.
oliverlaz
requested review from
MartinCupela,
isekovanic,
santhoshvai,
szuperaz and
vishalnarkhede
as code owners
August 21, 2026 09:40
`Streami18n.brand` existed so a UI SDK could recognize an instance across two physical copies of this package, where `instanceof` fails. Both SDKs have stopped checking, so it has no consumer left in any of the three repos. It was also blind to the case that actually bites. The brand sits on *core's* class, so a bare `new Streami18n()` from `stream-chat/i18n` -- carrying none of a UI SDK's bundled `runtimeDefaults`, and so rendering `timestamp.*` and `language.*` as raw dotted keys -- passed it cleanly. All it ever rejected was a value that is not a `Streami18n` at all, which the prop types already cover. BREAKING CHANGE: `Streami18n.brand` is removed. Nothing in either UI SDK read it; a custom integration that recognized instances through it should compare against its own marker, or accept the instance it was handed.
MartinCupela
approved these changes
Aug 21, 2026
isekovanic
approved these changes
Aug 21, 2026
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 21, 2026
## [10.0.0-rc.6](v10.0.0-rc.5...v10.0.0-rc.6) (2026-08-21) ### ⚠ BREAKING CHANGES * remove hand-written types and API wrappers (#1836) * **i18n:** `Streami18n.init()` now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. The instance remains usable in its degraded English form either way. `Streami18n.brand` is removed; nothing in either UI SDK read it, and a custom integration that recognized instances through it should compare against its own marker or accept the instance it was handed. ### Bug Fixes * **i18n:** reject from init() and drop the Streami18n brand static ([#1839](#1839)) ([67bb1c7](67bb1c7)) ### Features * remove hand-written types and API wrappers ([#1836](#1836)) ([dc49a78](dc49a78))
|
🎉 This PR is included in version 10.0.0-rc.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related breaking changes to
Streami18n, both of which move a decision out of core and into the caller. They ship together because the second is only safe once the first has landed in both UI SDKs.1.
init()rejects instead of swallowingIt caught every failure, logged a string, and resolved:
A consumer had no way to know initialization had failed short of watching the log, and an integrator who supplied a
loggerto route diagnostics somewhere quiet lost the signal entirely. The original error — stack, cause, i18next's own detail — was flattened to a string and dropped. Swallowing at a library boundary is the wrong default; the caller owns the policy.The old justification was "neither UI SDK awaits this, so a rejection would be unhandled". That is a statement about the SDKs, not about the contract — so the SDKs are what got fixed, in the companion PRs below.
init()now rejects with the original error, and core no longer logs. A failure is reported once, by whoever handles it, in a form they can inspect.What does not change is the degraded-but-safe guarantee, which is a separate concern worth keeping distinct:
init()initializedfalsefalsetregisterTranslation/setLanguageThe promise reports the failure; the store keeps the UI alive. Rejecting does not mean the instance is unusable.
Two things fall out:
init()retries rather than replaying one rejection for the lifetime of the instance. Concurrent callers still share the in-flight promise, which is the only thing the memo was ever for.catchstill sees the rejection. There is a test for exactly that, because getting it wrong silently swallows the rejection again.2.
Streami18n.brandis removedstatic readonly brand = Symbol.for('stream-chat.Streami18n')existed so a UI SDK could recognize an instance across two physical copies of this package, whereinstanceoffails. Both SDKs have now stopped checking, so it has no consumer left in any of the three repos — verified by grep across all of them.It was also blind to the case that actually bites. The brand sits on core's class, so a bare
new Streami18n()fromstream-chat/i18n— carrying none of a UI SDK's bundledruntimeDefaults, and therefore renderingtimestamp.*andlanguage.*as raw dotted keys — passed the check cleanly. All it ever rejected was a value that is not aStreami18nat all, which the prop types already cover.Companion PRs
Both UI SDKs catch the rejection in their
useStreami18neffect and report throughconsole.warn, which keeps theErrorobject and its stack intact. Neither surfaces an error state, because the degraded instance still renders English and failing a whole chat subtree over a translation-layer fault would be the worse outcome. Both also dropped the brand check.Their hook tests mock
init()itself rather than the i18next instance underneath — what is under test there is the hook's handling of a rejection, not core's decision to reject, and it keeps those tests independent of whichstream-chatversion is installed while this rolls out.Verification
yarn lint0,yarn types0, 2,881 tests. The rewrittenStreami18n — a failed init()block pins the new contract: rejects with the original error, does not log, leavesinitializedfalse, keeps rendering inline English, does not breakregisterTranslation/setLanguage, retries on a later call, and shares the in-flight promise with concurrent callers before failing.Sequencing
This needs an rc after merge — the SDK-side
catchis inert against rc.5, which still resolves — and then a version bump in both consumers.BREAKING CHANGE:
Streami18n.init()now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. The instance remains usable in its degraded English form either way.Streami18n.brandis removed; nothing in either UI SDK read it, and a custom integration that recognized instances through it should compare against its own marker or accept the instance it was handed.