TL;DR
-
Messaging: Fixed a silent settings synchronization bug by introducing typed, namespaced messages.
-
Hover Translation: Added a hover-to-translate popup with language selection and race-condition-safe handling.
-
Default Language: Users can now configure a default translation language that syncs across tabs.
-
Error Feedback: OCR and translation failures now show toast notifications instead of silently failing.
-
Domain Rules: Fixed empty domain lists incorrectly allowing translation everywhere.
-
Auto-translate: Restored automatic translation behavior that was accidentally removed during the refactor.
Unified messaging
I discovered a pretty annoying bug in the settings synchronization system.
The popup was sending:
ts"notify-settings-changed"
while the background script was listening for:
ts"settings/notify-changed"
Because of this mismatch, settings changes were not propagated correctly and failed silently.
To avoid this kind of problem in the future, I introduced types/messages.ts with typed, namespaced message routing. Now the message names are centralized instead of being manually written across different parts of the extension.
Settings changes can now propagate correctly and update active tabs in real time.
Hover translation popup
The overlay can now translate individual OCR regions directly through a hover popup.
When hovering over an OCR box, the extension displays:
-
The original detected text
-
A target language selector
-
The translated result
The popup uses a small debounce before appearing and has a delayed close behavior when the mouse leaves. If the user moves back into the popup, the close action is cancelled.
I also had to handle race conditions. Translation requests can finish in a different order than they were created, so late responses are ignored when they no longer belong to the currently active popup.
The popup handles its own translation request and lifecycle, which makes it much more self-contained.
Default translation language (model error)
Users can now choose a default target language in Settings.
The current default is Vietnamese (vi), with support for 17 languages. The setting is stored using browser.storage.sync and changes are broadcast to active tabs.
The language selected inside a hover popup is temporary, while the configured default language remains the user’s persistent preference.
In the future we will switch from machine translate or deep learning translate
Error feedback instead of silent failures
Previously, OCR and translation errors could fail without giving the user any useful feedback.
I added utils/toast.ts to provide inline error notifications. Errors are debounced and deduplicated so the user does not get spammed with notifications when multiple failures happen at once.
I also extracted shared popup styling into utils/popup-base.ts to reduce duplicated UI logic.
Empty domains fix
There was another logic issue with enabledDomains.
An empty domain list should mean:
Translate nothing.
However, the previous implementation treated an empty list as if every domain was allowed.
This has now been fixed in both background.ts and isDomainAllowed(). An empty list correctly disables translation entirely.
Auto-translate restored
During the refactor, some of the automatic translation logic was accidentally removed.
This included:
autoTranslateIfAllowed()- URL polling
- The live DOM observer
As a result, the extension would remain inactive until the user manually triggered it.
These behaviors have now been restored, so automatic translation works again when the page loads and when new content appears dynamically.