Skip to content

fix: synchronize language state migration across browser tabs - #1175

Merged
itzzavdhesh merged 2 commits into
itzzavdhesh:mainfrom
hrshjswniii:BugFix/Inconsistent-Language-State-Migration
Aug 6, 2026
Merged

fix: synchronize language state migration across browser tabs#1175
itzzavdhesh merged 2 commits into
itzzavdhesh:mainfrom
hrshjswniii:BugFix/Inconsistent-Language-State-Migration

Conversation

@hrshjswniii

@hrshjswniii hrshjswniii commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🚀 Program

GSSoC

📝 Description

This PR resolves language state desynchronization across open browser tabs and components in VoiceForge when users change their output language setting.

Key fixes:

  1. Language Event Dispatcher (languages.js): Refactored persistLanguage(code) in languages.js to dispatch a voiceforge:languageChanged custom window event upon updating the language setting.
  2. Cross-Tab & Local Subscription Helper (subscribeLanguageChange): Exported subscribeLanguageChange in languages.js listening to both local voiceforge:languageChanged events and multi-tab browser storage events for key migration and synchronization.
  3. Component & Page Subscriptions (Call.jsx, VoiceForge.jsx, Settings.jsx): Subscribed Call.jsx, VoiceForge.jsx, and Settings.jsx to subscribeLanguageChange so that language updates in any tab update active component states dynamically without requiring a page refresh.
  4. Unit Tests (languages.test.js): Added Vitest test assertions for language loading, legacy key migration, event dispatching, and synchronization helpers.

🔗 Related Issue

Closes #1132

🔄 Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🔍 SEO improvement
  • 🎨 Style / UI improvement
  • ♿ Accessibility improvement
  • 📝 Documentation
  • ⚙️ CI / configuration
  • 🧹 Refactor / cleanup

🧪 How to Test

  1. Open VoiceForge in two browser tabs side-by-side (Tab A: Settings, Tab B: Call).
  2. On Tab A, change the output language from "English" to "French".
  3. Switch to Tab B without refreshing the page and verify the active language updates to "French" automatically.
  4. Run client tests: npm run test --workspace client and verify all tests pass.

📸 Screenshots (if applicable)

✅ Checklist

  • I am contributing under GSSoC
  • My code follows the project's existing style
  • I have tested my changes in a browser
  • I have linked the related issue above
  • My PR title follows Conventional Commits format (e.g. fix: synchronize language state migration across browser tabs)

Summary by cubic

Fixes language desync across tabs and components. Language changes now update instantly everywhere and migrate the legacy voiceforge:compose-language key. Fixes #1132.

  • Bug Fixes

    • persistLanguage dispatches voiceforge:languageChanged after saving.
    • subscribeLanguageChange listens to the custom event and storage changes (including legacy key and storage clears); returns an unsubscribe.
    • VoiceForge.jsx, Call.jsx, and Settings.jsx subscribe for live state updates without refresh.
  • Tests

    • Added Vitest coverage for loading, legacy key migration, event dispatch, cross-tab sync, language code validation, and getLanguageByCode.

Written for commit a15b6d3. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Language changes now update VoiceForge, Call, and Settings views automatically.
    • Language preferences stay synchronized across browser tabs and sessions.
    • Legacy saved language preferences are migrated automatically.
  • Bug Fixes

    • Prevented language settings from becoming outdated when changed elsewhere in the app.
  • Tests

    • Added coverage for language validation, persistence, migration, and synchronization.

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@hrshjswniii is attempting to deploy a commit to the itzzavdhesh's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown

✍️ DCO Sign-off Needed

Hey @hrshjswniii! 👋 One or more commits in this PR are missing a Signed-off-by: line.

Warning

  • 10479a6 fix: synchronize language state migration across browser tabs

How to fix:

For the latest commit:

git commit --amend --signoff
git push --force-with-lease

For multiple commits, replace N with the number to update:

git rebase --signoff HEAD~N
git push --force-with-lease

This comment will update automatically after you push.


🤖 VoiceForge Automation · Updates automatically on edits

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

🎉 PR Ready for Mentor Review

Hey @hrshjswniii! 👋 Your PR passed all checks and is now in the GSSoC review queue.

Note

🔗 Closing: #1132 · 📐 120 lines across 5 file(s) · 📬 Already requested or no eligible reviewer found

@sabeenaviklar @Anushreebasics @itsdakshjain @snehkris @Mrigakshi-Rathore @Itzzavdheshh @Nitya-003 @4f4d @lovestaco, this PR is ready for your review — please confirm scope, check behavior and tests, then approve or request changes.

Important

This is not an approval. Please wait for mentor feedback before expecting a merge. If changes are requested, push them to this same branch and keep the PR focused on the linked issue.


🤖 VoiceForge Automation · Updates automatically on edits

@github-actions
github-actions Bot requested a review from lovestaco July 27, 2026 04:17
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Language persistence now emits custom and storage events, while VoiceForge, Call, and Settings subscribe and update their local language state. Tests cover validation, legacy-key migration, persistence, and synchronization.

Changes

Language synchronization

Layer / File(s) Summary
Language event contract and validation
client/src/utils/languages.js, client/src/utils/languages.test.js
Persistence dispatches language-change events; subscriptions handle in-tab, cross-tab, and legacy-key updates. Tests cover validation, migration, persistence, and callbacks.
Consumer state synchronization
client/src/components/VoiceForge.jsx, client/src/pages/Call.jsx, client/src/pages/Settings.jsx
Components subscribe to external language changes, update local state, and clean up subscriptions where implemented.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Settings
  participant LanguageUtilities
  participant Browser
  participant Call
  participant VoiceForge
  Settings->>LanguageUtilities: persistLanguage(language)
  LanguageUtilities->>Browser: write storage and dispatch change event
  Browser-->>Call: deliver storage or custom event
  Browser-->>VoiceForge: deliver storage or custom event
  Call->>Call: update language state
  VoiceForge->>VoiceForge: update language state
Loading

Possibly related PRs

Suggested labels: level:intermediate

Suggested reviewers: itzzavdheshh, vivek0028, snehkris, anushreebasics

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #1132 by broadcasting language updates and subscribing components to storage and custom events.
Out of Scope Changes check ✅ Passed The added tests and component subscriptions are directly related to the language-sync fix, with no obvious unrelated changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: synchronizing language state across browser tabs and components.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch BugFix/Inconsistent-Language-State-Migration

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/src/utils/languages.test.js (1)

41-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the cross-tab and cleanup paths explicitly.

This only exercises the same-tab custom event. Add assertions for a relevant storage event and for no callback after unsubscribe(); otherwise the primary cross-tab behavior can regress unnoticed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/utils/languages.test.js` around lines 41 - 54, Add coverage in the
“persists language and dispatches custom change event” test for a relevant
storage event representing a language change, asserting the subscribed callback
receives the new language. After calling unsubscribe, trigger the same event
path again and assert the callback is not invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/utils/languages.js`:
- Around line 101-108: Update the language-setting flow around
localStorage.setItem and the custom event dispatch so storage persistence
remains best-effort while window.dispatchEvent runs independently even when
persistence throws. Preserve the existing fallback value, event payload, and
environment/function guards, and keep storage errors from preventing the in-tab
language update.

---

Nitpick comments:
In `@client/src/utils/languages.test.js`:
- Around line 41-54: Add coverage in the “persists language and dispatches
custom change event” test for a relevant storage event representing a language
change, asserting the subscribed callback receives the new language. After
calling unsubscribe, trigger the same event path again and assert the callback
is not invoked.
🪄 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 Plus

Run ID: 9b4a5f44-3963-4157-9998-709f4991a982

📥 Commits

Reviewing files that changed from the base of the PR and between b89b5be and 10479a6.

📒 Files selected for processing (5)
  • client/src/components/VoiceForge.jsx
  • client/src/pages/Call.jsx
  • client/src/pages/Settings.jsx
  • client/src/utils/languages.js
  • client/src/utils/languages.test.js

Comment on lines 101 to 108
try {
localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
const val = code || "en";
localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
}
} catch {
// Storage unavailable - continue without persisting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Dispatch the local event even when storage fails.

A localStorage.setItem quota/security exception exits the shared try before the custom event is dispatched, so other mounted components in the same tab remain stale. Keep storage persistence best-effort, but dispatch the in-tab update independently.

Proposed fix
 export function persistLanguage(code) {
+  const val = code || "en";
   try {
-    const val = code || "en";
     localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
-    if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
-      window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
-    }
   } catch {
     // Storage unavailable - continue without persisting.
   }
+
+  if (
+    typeof window !== "undefined" &&
+    typeof window.dispatchEvent === "function" &&
+    typeof CustomEvent === "function"
+  ) {
+    window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
const val = code || "en";
localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
}
} catch {
// Storage unavailable - continue without persisting.
export function persistLanguage(code) {
const val = code || "en";
try {
localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
} catch {
// Storage unavailable - continue without persisting.
}
if (
typeof window !== "undefined" &&
typeof window.dispatchEvent === "function" &&
typeof CustomEvent === "function"
) {
window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/utils/languages.js` around lines 101 - 108, Update the
language-setting flow around localStorage.setItem and the custom event dispatch
so storage persistence remains best-effort while window.dispatchEvent runs
independently even when persistence throws. Preserve the existing fallback
value, event payload, and environment/function guards, and keep storage errors
from preventing the in-tab language update.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

4 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="client/src/utils/languages.js">

<violation number="1" location="client/src/utils/languages.js:102">
P2: A truthy unsupported code now immediately puts subscribed components into an invalid language state, while a reload resolves the same stored value to `en`. Normalize `val` with `VALID_CODES` before persisting and dispatching so local and cross-tab updates agree.</violation>

<violation number="2" location="client/src/utils/languages.js:104">
P2: The in-tab event dispatch is inside the `try` block that wraps `localStorage.setItem`. If storage throws a quota or security exception (a scenario the existing `catch` already anticipates), the `CustomEvent` is never dispatched and other mounted components in the same tab won't receive the language update — defeating the purpose of this synchronization change.

Move the event dispatch outside the `try`/`catch` so that in-tab notification remains independent of storage success.</violation>

<violation number="3" location="client/src/utils/languages.js:126">
P2: An active older Compose tab changing its legacy key will not update newer tabs once they already have `voiceforge:language`; `loadLanguage()` ignores the new legacy value when the unified key exists. Handle a non-null legacy storage value as the incoming update and migrate/publish it, rather than reloading the existing unified value.</violation>
</file>

<file name="client/src/utils/languages.test.js">

<violation number="1" location="client/src/utils/languages.test.js:13">
P0: All assertions for legacy key migration, localStorage persistence, event dispatching, and cross-tab subscription are silently skipped because the vitest environment is `node` (no `localStorage` or `window` available). The three environmental guards (`if (typeof localStorage !== "undefined")` and `if (typeof window !== "undefined" && typeof window.dispatchEvent === "function")`) wrap the critical assertions, so the tests pass green without ever executing them. The legacy migration test and the event dispatch/subscription test are vacuous — they look like coverage but test nothing.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@@ -0,0 +1,56 @@
import { describe, it, expect, beforeEach, vi } from "vitest";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0: All assertions for legacy key migration, localStorage persistence, event dispatching, and cross-tab subscription are silently skipped because the vitest environment is node (no localStorage or window available). The three environmental guards (if (typeof localStorage !== "undefined") and if (typeof window !== "undefined" && typeof window.dispatchEvent === "function")) wrap the critical assertions, so the tests pass green without ever executing them. The legacy migration test and the event dispatch/subscription test are vacuous — they look like coverage but test nothing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client/src/utils/languages.test.js, line 13:

<comment>All assertions for legacy key migration, localStorage persistence, event dispatching, and cross-tab subscription are silently skipped because the vitest environment is `node` (no `localStorage` or `window` available). The three environmental guards (`if (typeof localStorage !== "undefined")` and `if (typeof window !== "undefined" && typeof window.dispatchEvent === "function")`) wrap the critical assertions, so the tests pass green without ever executing them. The legacy migration test and the event dispatch/subscription test are vacuous — they look like coverage but test nothing.</comment>

<file context>
@@ -0,0 +1,56 @@
+
+describe("languages.js utility and multi-tab synchronization", () => {
+  beforeEach(() => {
+    if (typeof localStorage !== "undefined") {
+      localStorage.clear();
+    }
</file context>

export function persistLanguage(code) {
try {
localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
const val = code || "en";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: A truthy unsupported code now immediately puts subscribed components into an invalid language state, while a reload resolves the same stored value to en. Normalize val with VALID_CODES before persisting and dispatching so local and cross-tab updates agree.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client/src/utils/languages.js, line 102:

<comment>A truthy unsupported code now immediately puts subscribed components into an invalid language state, while a reload resolves the same stored value to `en`. Normalize `val` with `VALID_CODES` before persisting and dispatching so local and cross-tab updates agree.</comment>

<file context>
@@ -99,12 +99,46 @@ export function loadLanguage() {
 export function persistLanguage(code) {
   try {
-    localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
+    const val = code || "en";
+    localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
+    if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
</file context>
Suggested change
const val = code || "en";
const val = VALID_CODES.has(code) ? code : "en";

function handleStorageEvent(e) {
if (
e.key === LANGUAGE_STORAGE_KEY ||
e.key === "voiceforge:compose-language" ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: An active older Compose tab changing its legacy key will not update newer tabs once they already have voiceforge:language; loadLanguage() ignores the new legacy value when the unified key exists. Handle a non-null legacy storage value as the incoming update and migrate/publish it, rather than reloading the existing unified value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client/src/utils/languages.js, line 126:

<comment>An active older Compose tab changing its legacy key will not update newer tabs once they already have `voiceforge:language`; `loadLanguage()` ignores the new legacy value when the unified key exists. Handle a non-null legacy storage value as the incoming update and migrate/publish it, rather than reloading the existing unified value.</comment>

<file context>
@@ -99,12 +99,46 @@ export function loadLanguage() {
+  function handleStorageEvent(e) {
+    if (
+      e.key === LANGUAGE_STORAGE_KEY ||
+      e.key === "voiceforge:compose-language" ||
+      !e.key
+    ) {
</file context>

localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
const val = code || "en";
localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The in-tab event dispatch is inside the try block that wraps localStorage.setItem. If storage throws a quota or security exception (a scenario the existing catch already anticipates), the CustomEvent is never dispatched and other mounted components in the same tab won't receive the language update — defeating the purpose of this synchronization change.

Move the event dispatch outside the try/catch so that in-tab notification remains independent of storage success.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client/src/utils/languages.js, line 104:

<comment>The in-tab event dispatch is inside the `try` block that wraps `localStorage.setItem`. If storage throws a quota or security exception (a scenario the existing `catch` already anticipates), the `CustomEvent` is never dispatched and other mounted components in the same tab won't receive the language update — defeating the purpose of this synchronization change.

Move the event dispatch outside the `try`/`catch` so that in-tab notification remains independent of storage success.</comment>

<file context>
@@ -99,12 +99,46 @@ export function loadLanguage() {
-    localStorage.setItem(LANGUAGE_STORAGE_KEY, code || "en");
+    const val = code || "en";
+    localStorage.setItem(LANGUAGE_STORAGE_KEY, val);
+    if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
+      window.dispatchEvent(new CustomEvent("voiceforge:languageChanged", { detail: val }));
+    }
</file context>

@Nitya-003 Nitya-003 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrshjswniii Resolve the merge conflicts and comments by bot.

Image

@itsdakshjain itsdakshjain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good work , Resolve the merge conflicts

@itsdakshjain itsdakshjain added the mentor:itsdakshjain GSSoC: Mentor-@itsdakshjain label Jul 29, 2026
@itzzavdhesh
itzzavdhesh removed the request for review from 1754riya August 4, 2026 13:18
@itzzavdhesh
itzzavdhesh merged commit 6ea9e83 into itzzavdhesh:main Aug 6, 2026
6 of 11 checks passed
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🎊 PR Merged Successfully

Hey @hrshjswniii! 👋 Congratulations and thank you for your contribution to VoiceForge!

Note

🔗 Linked issue(s): #1132 · ✅ Marked as merged and complete

Maintainers may still handle final cleanup, release notes, or follow-up tracking after the merge.


🤖 VoiceForge Automation · Updates automatically on edits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] : Inconsistent Language State Migration Across Tabs

4 participants