Skip to content

Feat: Browser-native message translation (distinct from UI localization)#173

Open
louzt wants to merge 4 commits into
obbyworld:mainfrom
louzt:feat/issue-101-translation-exploration
Open

Feat: Browser-native message translation (distinct from UI localization)#173
louzt wants to merge 4 commits into
obbyworld:mainfrom
louzt:feat/issue-101-translation-exploration

Conversation

@louzt
Copy link
Copy Markdown

@louzt louzt commented Apr 22, 2026

Summary

This PR adds an optional client-side translation action to individual messages as progressive enhancement. It is intentionally scoped as a local browser feature and does not claim to implement issue #101 as written, since #101 asks for an IRC-side/server-side translation facility.

This PR focuses exclusively on dynamic chat message translation. Unlike the i18n efforts in #171 (which localize the static UI/dictionaries), this implementation uses the Browser AI APIs to translate real-time user content on demand. Both features are complementary: #171 makes the app's interface accessible, while this PR makes the conversation accessible.

What this does

  • Adds a per-message Translate action in desktop and mobile message actions.
  • Uses the browser Translator API only when the runtime supports it.
  • Requires secure context and explicit user interaction.
  • Keeps the original message content unchanged and renders translated output as a secondary layer.
  • Adds an explicit target-language preference, with browser language as fallback.

What this does not do

  • No IRC-side translation.
  • No automatic translation of all incoming messages.
  • No backend dependency in this PR.
  • No claim that this resolves Automatic language translation Automatic language translation #101 end-to-end.

Follow-up hardening included on this branch

Since opening the PR, this branch also picked up narrow hardening specific to the browser-translation flow itself:

  • stale-request / unmount-safe translation lifecycle handling so older async runs cannot overwrite newer UI state
  • detector-quality guards for short text, low-confidence results, and und
  • deterministic focused tests for the browser-translation path
  • mirrored component test placement for MessageItem

How this now composes with the other translation tracks

This PR still stands on its own as a browser-native, backend-free enhancement.

That said, the surrounding follow-up work means the client-side path is now better prepared for maintainers who want to evaluate a self-hosted backend later:

So while this PR does not itself add a backend, it does help prove and stabilize the user-facing interaction model. Combined with #174 and #175, the remaining work for a self-hosted backend experiment shifts more toward deployment / proxy / service configuration than toward re-inventing the frontend UX.

Self-hosted deployment follow-up

For maintainers who want a concrete next-step document for self-hosted translation infrastructure, I wrote a separate follow-up note here:

That note is intentionally framed as deployment guidance and adapter follow-up, not as proof that #173 already delivers IRC-side translation.

Why this split is useful

This gives users an immediate privacy-preserving local enhancement where supported, while keeping the door open for:

  • a separate adapter-driven fallback
  • self-hosted backend deployments
  • or a future IRC-side solution

It also keeps review boundaries cleaner:

Standards reference

MDN Translator API:
https://developer.mozilla.org/en-US/docs/Web/API/Translator

MDN Translator and Language Detector APIs:
https://developer.mozilla.org/en-US/docs/Web/API/Translator_and_Language_Detector_APIs/Using

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

Adds browser-based message translation: new browserTranslation library, UI integration in message components and mobile sheet, a new global setting for translation target language with store/types updates, and comprehensive tests for detection, availability, and translation flows.

Changes

Cohort / File(s) Summary
Core Translation Library
src/lib/browserTranslation.ts
New module: language tag normalization, preferred-target logic, source-language extraction, availability probing, detection, and translateWithBrowser (secure-context, abortable, progress).
Message UI & Actions
src/components/message/MessageActions.tsx, src/components/message/ReactionsWithActions.tsx, src/components/message/MessageItem.tsx, src/components/mobile/MessageBottomSheet.tsx
Threaded translate props (onTranslate*, canTranslate, isTranslating); MessageItem adds translation state machine, async guarded/abortable flow, banners, rendered translated markdown, and toolbar overlap bbox fix; bottom sheet adds Translate action and disabled/styled state.
Store, Types & Settings
src/store/index.ts, src/store/types.ts, src/lib/settings/definitions/allSettings.ts
Added translationTargetLanguage to GlobalSettings and initial store state; new preference preferences.translationTargetLanguage; bumped priorities for several existing prefs.
Tests — Translation & Mocks
tests/lib/browserTranslation.test.ts, tests/components/MessageItem.translation.test.tsx, tests/components/MessageActions.test.tsx, tests/store/index.test.ts, tests/store/localStorage.test.ts
New tests covering language canonicalization, preferred-target selection, detection, availability mapping, translateWithBrowser lifecycle/progress/destroy, MessageItem translation flows (concurrency), and MessageActions UI/interaction.
Tests — Fixtures Updated
tests/App.test.tsx, tests/components/.../ChannelSettingsModal.test.tsx, tests/components/.../LinkSecurityWarningModal.test.tsx, tests/components/.../MediaCommentsSidebar.test.tsx, tests/components/.../MetadataDisplay.test.tsx, tests/store/join.test.ts
Updated mocked test fixtures to include globalSettings.translationTargetLanguage: "".
Repo Config
.gitignore
Added /.fork-local ignore rule.

Sequence Diagram

sequenceDiagram
    participant User
    participant MessageUI as Message UI
    participant TranslationFlow as Translation Handler
    participant BrowserAPI as Browser Translator API
    participant Store as State Store
    participant DOM

    User->>MessageUI: Click "Translate"
    MessageUI->>TranslationFlow: onTranslateClick()
    TranslationFlow->>Store: read translationTargetLanguage
    TranslationFlow->>BrowserAPI: getBrowserTranslationAvailability(source,target)
    alt unavailable / insecure / unsupported
        BrowserAPI-->>TranslationFlow: unavailable|unsupported|insecure-context
        TranslationFlow->>Store: set translationState.error
        TranslationFlow->>DOM: render error banner
    else available / downloadable / downloading
        BrowserAPI-->>TranslationFlow: availability
        TranslationFlow->>Store: set downloading/translating
        TranslationFlow->>BrowserAPI: translateWithBrowser({source,target,text,signal,onDownloadProgress})
        BrowserAPI->>BrowserAPI: create translator, emit downloadprogress
        BrowserAPI-->>TranslationFlow: translated text
        TranslationFlow->>Store: set translated state
        Store->>DOM: render translated content banner
    end
    DOM-->>User: show translation result/status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ValwareIRC
  • matheusfillipe

Poem

🐰 I nibble tags and hop through code,

words that wandered find a common road.
Click the button, watch sentences play—
Hopping meanings, bright as day. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements client-side per-message translation, not IRC-side automatic translation as requested in issue #101. Issue #101 requires server-side translation for efficiency and multi-user serving, which this PR does not implement. Either implement IRC-side automatic translation as specified in issue #101, or update the issue to clarify that browser-native client-side translation is an acceptable alternative approach.
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat: Browser-native message translation (distinct from UI localization)' clearly and specifically describes the main feature being added: client-side browser-native message translation functionality.
Out of Scope Changes check ✅ Passed All changes are scoped to adding browser-native message translation: new translation utilities, UI components, settings, state management, tests, and .gitignore updates. No unrelated changes are present.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/store/index.ts (1)

941-942: Drop the redundant section comment.

Line 942 is self-explanatory, so the new comment on Line 941 can be removed.

♻️ Proposed cleanup
-    // Translation settings
     translationTargetLanguage: "",

As per coding guidelines, Comments should explain why, not what; omit comments if code is self-explanatory; keep comments to one line in most cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/index.ts` around lines 941 - 942, Remove the redundant one-line
comment "// Translation settings" that precedes the translationTargetLanguage
property; the property translationTargetLanguage: "" is self-explanatory so
simply delete that comment line near the translationTargetLanguage declaration
(in the same block) to comply with the guideline that comments explain why not
what.
src/lib/settings/definitions/allSettings.ts (1)

252-265: Consider validating the BCP 47 input.

The description advertises BCP 47 semantics ("e.g. en, es, ja"), but the setting accepts any free-form string and is consumed downstream by Intl/Translator APIs. An invalid tag will silently fail at translation time with no user feedback. A lightweight validation.pattern (e.g. /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/) or an Intl.getCanonicalLocales check in the consumer would catch typos early.

🛠️ Proposed fix
     type: "text",
     defaultValue: "",
     placeholder: "e.g. en, es, ja",
     searchKeywords: ["translate", "translation", "language", "locale"],
     priority: 3,
+    validation: {
+      pattern: /^$|^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/,
+    },
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/settings/definitions/allSettings.ts` around lines 252 - 265, The
translationTargetLanguage setting (id "preferences.translationTargetLanguage",
key "translationTargetLanguage") currently accepts any string; add validation to
ensure BCP 47-like tags so invalid values are caught early — either add a
validation.pattern property (e.g. a regex for primary language subtag plus
optional extensions) and a helpful validation message to the settings object in
allSettings.ts, or validate/canonicalize the value in the consumer using
Intl.getCanonicalLocales (e.g. in the Translator or wherever the setting is
read) and surface a user-facing error or fallback when the tag is invalid;
update the setting definition and the consumer (Translator/Intl usage)
accordingly to prevent silent failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/message/MessageItem.tsx`:
- Around line 981-985: The translated HTML is rendered outside the link-warning
flow so URLs in translatedHtmlContent can bypass ExternalLinkWarningModal; wrap
the translated content with the same EnhancedLinkWrapper used for the original
message (i.e. render translatedHtmlContent inside <EnhancedLinkWrapper ...> when
translationState.status === "translated") so that clicks still route through the
app's external-link handling and openExternalUrl/ExternalLinkWarningModal path;
ensure you pass the same props/handlers to EnhancedLinkWrapper that the original
message uses so the warning flow is preserved.

In `@src/components/mobile/MessageBottomSheet.tsx`:
- Around line 71-81: The Translate action is still clickable while
isTranslating, causing duplicate requests; update the action object pushed
inside the canTranslate && onTranslate block (the object with
label/icon/onClick/className) to prevent clicks when isTranslating by either 1)
adding a disabled: isTranslating flag to that action and make the action
renderer (the component that maps actions to a <button>) consume and pass
disabled to the button, or 2) guard the existing onClick handler to no-op when
isTranslating (e.g., return early if isTranslating before calling onTranslate()
and onClose()). Ensure the change references the same action shape used by
MessageBottomSheet so the rendered button becomes non-interactive and accessible
while translating.

In `@src/lib/browserTranslation.ts`:
- Around line 23-31: The function normalizeTranslationLanguageTag currently
truncates BCP 47 tags by splitting on "-" and returning only the primary subtag;
update normalizeTranslationLanguageTag to preserve the full BCP 47 tag
(including script/region subtags) by simply trimming and returning the full
trimmed string (or null for empty input) instead of splitting and returning only
primarySubtag; keep the function name normalizeTranslationLanguageTag and ensure
it still returns string | null.
- Around line 57-67: The getMessageSourceLanguage function currently defaults to
"en" when no language tags exist; change it to return string | null instead and
stop defaulting to "en" so callers can handle unknown language explicitly (e.g.,
call the Language Detector API). Update the implementation in
getMessageSourceLanguage (and any uses) to return
normalizeTranslationLanguageTag(...) ?? null (remove the "en" fallback) and
adjust callers to detect language via the Language Detector API or disable
translation when null is returned; keep references to
normalizeTranslationLanguageTag and getMessageSourceLanguage to locate and
update all call sites.

In `@tests/components/MessageItem.translation.test.tsx`:
- Around line 154-198: The current test only verifies that MessageItem calls
getPreferredTranslationTargetLanguageFromSetting (mockTargetLanguage) with "es"
but doesn't exercise the explicit-vs-fallback branch inside that helper; add a
companion unit test in tests/lib/browserTranslation.test.ts that imports
getPreferredTranslationTargetLanguageFromSetting (or the exported helper) and
asserts that when mockState.globalSettings.translationTargetLanguage is ""
(empty string) the function returns the browser language (simulate
navigator.language or mock its getter) and when it's "es" it returns "es";
alternatively, in the existing MessageItem test remove/move the mock of
getPreferredTranslationTargetLanguageFromSetting so the real helper runs, or add
a second MessageItem test that sets translationTargetLanguage="" and verifies
mockTranslate is called with targetLanguage equal to the browser locale to cover
the fallback path.

In `@tests/lib/browserTranslation.test.ts`:
- Around line 32-55: The tests overwrite window.navigator and leak its mocked
descriptor across tests; update the test file to save the original navigator
descriptor before each modification and restore it in an afterEach (or use
vi.stubGlobal("navigator", ...) to stub and automatically restore), ensuring
calls that set navigator for getPreferredTranslationTargetLanguage and
getPreferredTranslationTargetLanguageFromSetting are reverted so later tests
aren't order-dependent.

---

Nitpick comments:
In `@src/lib/settings/definitions/allSettings.ts`:
- Around line 252-265: The translationTargetLanguage setting (id
"preferences.translationTargetLanguage", key "translationTargetLanguage")
currently accepts any string; add validation to ensure BCP 47-like tags so
invalid values are caught early — either add a validation.pattern property (e.g.
a regex for primary language subtag plus optional extensions) and a helpful
validation message to the settings object in allSettings.ts, or
validate/canonicalize the value in the consumer using Intl.getCanonicalLocales
(e.g. in the Translator or wherever the setting is read) and surface a
user-facing error or fallback when the tag is invalid; update the setting
definition and the consumer (Translator/Intl usage) accordingly to prevent
silent failures.

In `@src/store/index.ts`:
- Around line 941-942: Remove the redundant one-line comment "// Translation
settings" that precedes the translationTargetLanguage property; the property
translationTargetLanguage: "" is self-explanatory so simply delete that comment
line near the translationTargetLanguage declaration (in the same block) to
comply with the guideline that comments explain why not what.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a263465-7172-40cd-b1eb-8c0412c4b0ff

📥 Commits

Reviewing files that changed from the base of the PR and between e731866 and ae92650.

📒 Files selected for processing (20)
  • .gitignore
  • src/components/message/MessageActions.tsx
  • src/components/message/MessageItem.tsx
  • src/components/message/ReactionsWithActions.tsx
  • src/components/mobile/MessageBottomSheet.tsx
  • src/lib/browserTranslation.ts
  • src/lib/settings/definitions/allSettings.ts
  • src/store/index.ts
  • src/store/types.ts
  • tests/App.test.tsx
  • tests/components/ChannelSettingsModal.test.tsx
  • tests/components/LinkSecurityWarningModal.test.tsx
  • tests/components/MediaCommentsSidebar.test.tsx
  • tests/components/MessageActions.test.tsx
  • tests/components/MessageItem.translation.test.tsx
  • tests/components/MetadataDisplay.test.tsx
  • tests/lib/browserTranslation.test.ts
  • tests/store/index.test.ts
  • tests/store/join.test.ts
  • tests/store/localStorage.test.ts

Comment thread src/components/message/MessageItem.tsx Outdated
Comment thread src/components/mobile/MessageBottomSheet.tsx
Comment thread src/lib/browserTranslation.ts Outdated
Comment thread src/lib/browserTranslation.ts
Comment thread tests/components/message/MessageItem.translation.test.tsx
Comment thread tests/lib/browserTranslation.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/components/MessageItem.translation.test.tsx (1)

1-1: Move this test under tests/components/message/.

This test covers src/components/message/MessageItem.tsx, so the mirrored path should be tests/components/message/MessageItem.translation.test.tsx.

As per coding guidelines, “Test files must mirror src/ directory structure and use Vitest with renderHook + act for React hooks.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/components/MessageItem.translation.test.tsx` at line 1, Move the test
file from tests/components/MessageItem.translation.test.tsx into a mirrored path
for the component under
tests/components/message/MessageItem.translation.test.tsx; update any relative
imports if needed and ensure test tooling matches project guidelines (use Vitest
runner and, where React hooks are exercised, use renderHook + act). Confirm the
test file name and directory match the source component
src/components/message/MessageItem.tsx and update any import paths or test
config references so the moved test still resolves component imports and runs
under Vitest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/message/MessageItem.tsx`:
- Around line 476-567: handleTranslateMessage can have its async results
overwrite newer state; add a stale-request guard using a requestId (useRef) that
you increment at the start of each translation attempt and capture into a local
const before any awaits, then before every setTranslationState (including in
onDownloadProgress, success branch, and catch) verify the captured id ===
current ref value and skip updates if not; also increment the requestId in a
useEffect cleanup on unmount so in-flight promises are ignored. Apply this guard
around calls to detectMessageSourceLanguage, getBrowserTranslationAvailability,
and translateWithBrowser (and any onDownloadProgress callbacks) so stale
responses don't update the UI.

In `@src/lib/browserTranslation.ts`:
- Around line 36-41: Update the BrowserLanguageDetectionResult interface to
include a confidence?: number field, and when calling the
BrowserLanguageDetectorInstance.detect(...) and using its first result (the
consumer around the current detection/usage site), guard the result by ensuring
result.detectedLanguage is not 'und' and result.confidence is above a reasonable
threshold (e.g., >= 0.5); if those checks fail, treat the detection as
undefined/fallback instead of using it as the source language to avoid
low-confidence or undetermined tags causing bad translations.

In `@tests/lib/browserTranslation.test.ts`:
- Around line 36-43: The test suite currently only calls setSecureContext(true)
in the afterEach cleanup which lets tests that run in isolation see
window.isSecureContext as false (causing detectMessageSourceLanguage to return
null); add a beforeEach that calls setSecureContext(true) (and any necessary
setup of window.navigator using originalNavigatorDescriptor if used) so the
secure context is initialized before each test, and keep the existing afterEach
cleanup (vi.unstubAllGlobals, vi.restoreAllMocks, setSecureContext(true),
Object.defineProperty(window, "navigator", originalNavigatorDescriptor)) to
restore state afterwards.

---

Nitpick comments:
In `@tests/components/MessageItem.translation.test.tsx`:
- Line 1: Move the test file from
tests/components/MessageItem.translation.test.tsx into a mirrored path for the
component under tests/components/message/MessageItem.translation.test.tsx;
update any relative imports if needed and ensure test tooling matches project
guidelines (use Vitest runner and, where React hooks are exercised, use
renderHook + act). Confirm the test file name and directory match the source
component src/components/message/MessageItem.tsx and update any import paths or
test config references so the moved test still resolves component imports and
runs under Vitest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d12f1c2-23bf-4fd7-b669-8e93f5468513

📥 Commits

Reviewing files that changed from the base of the PR and between ae92650 and a28aaa8.

📒 Files selected for processing (7)
  • src/components/message/MessageItem.tsx
  • src/components/mobile/MessageBottomSheet.tsx
  • src/lib/browserTranslation.ts
  • src/lib/settings/definitions/allSettings.ts
  • src/store/index.ts
  • tests/components/MessageItem.translation.test.tsx
  • tests/lib/browserTranslation.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/store/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/mobile/MessageBottomSheet.tsx
  • src/lib/settings/definitions/allSettings.ts

Comment thread src/components/message/MessageItem.tsx Outdated
Comment thread src/lib/browserTranslation.ts
Comment thread tests/lib/browserTranslation.test.ts
@louzt louzt changed the title Add experimental browser-native translation Feat: Browser-native message translation (distinct from UI localization) Apr 22, 2026
@louzt louzt force-pushed the feat/issue-101-translation-exploration branch from 46e17b4 to 9177071 Compare April 22, 2026 22:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/components/MessageItem.translation.test.tsx`:
- Line 1: The test file currently at
tests/components/MessageItem.translation.test.tsx should be moved to mirror the
source component path: create
tests/components/message/MessageItem.translation.test.tsx and delete the old
file; update any import paths inside the test that reference the component
(ensure imports point to src/components/message/MessageItem.tsx) and adjust any
test runner/config references if they rely on file globs so it continues to
match tests/**/*.test.{ts,tsx} conventions.

In `@tests/lib/browserTranslation.test.ts`:
- Around line 146-162: The tests rely on LanguageDetector/Translator being
absent but don't explicitly stub them, making behavior env-dependent; before the
tests that call detectMessageSourceLanguage, canUseBrowserTranslation, and
getBrowserTranslationAvailability, call vi.stubGlobal('LanguageDetector',
undefined) and vi.stubGlobal('Translator', undefined) to force those globals to
be undefined, and ensure you restore them after (e.g., in afterEach or afterAll
via vi.unstubAllGlobals or explicit re-stubs) so other tests are unaffected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4332f79f-34db-48bd-b441-1cce4b5139c4

📥 Commits

Reviewing files that changed from the base of the PR and between a28aaa8 and 46e17b4.

📒 Files selected for processing (4)
  • src/components/message/MessageItem.tsx
  • src/lib/browserTranslation.ts
  • tests/components/MessageItem.translation.test.tsx
  • tests/lib/browserTranslation.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/message/MessageItem.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/browserTranslation.ts

Comment thread tests/components/message/MessageItem.translation.test.tsx
Comment thread tests/lib/browserTranslation.test.ts
…er APIs

Align the MessageItem translation test with the mirrored component path expected by project review guidance, and make browserTranslation tests deterministic by explicitly stubbing missing LanguageDetector/Translator globals where absence is the behavior under test.
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.

1 participant