# How FluentRead Implements Context Menu Integration for Translations

> Discover how FluentRead integrates context menus for seamless translations. Learn about its use of browser APIs and state management for on-demand translation features.

- Repository: [ThinkStu/fluentread](https://github.com/bistutu/fluentread)
- Tags: how-to-guide
- Published: 2026-02-26

---

**FluentRead registers two browser context menu items—"全文翻译" (Translate Full Page) and "撤销翻译" (Restore Original)—in the background script, routes user clicks through the `browser.contextMenus` API to content scripts, and maintains per-tab translation state using a `Map` to dynamically enable or disable menu items based on the current page status.**

The open-source browser extension FluentRead (bistutu/fluentread) provides seamless context menu integration for translations, allowing users to translate entire web pages or revert to original content directly from the right-click menu. This functionality leverages the standard WebExtension `browser.contextMenus` API while implementing a robust state management system to track translation status across browser tabs.

## Architecture Overview

FluentRead’s context menu system follows a clean separation of concerns across three layers:

- **Constants layer** ([`entrypoints/utils/constant.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/constant.ts)) – Defines immutable menu IDs shared across the extension.
- **Background layer** ([`entrypoints/background.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/background.ts)) – Creates menu items, handles click events, manages per-tab state, and updates UI availability.
- **Content layer** ([`entrypoints/content.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/content.ts)) – Receives messages from the background script and executes page-level translation or restoration logic.

## Defining Menu Constants in constant.ts

To ensure type safety and prevent ID collisions, FluentRead centralizes context menu identifiers in a constants file. This approach allows the background and content scripts to reference the same string values without hardcoding literals.

```typescript
// entrypoints/utils/constant.ts
export const CONTEXT_MENU_IDS = {
    TRANSLATE_FULL_PAGE: 'fluent-read-translate-full-page',
    RESTORE_ORIGINAL:   'fluent-read-restore-original',
};

```

These constants are imported throughout the extension to register menu items and validate click events.

## Registering Context Menu Items in background.ts

When the extension initializes, the background script creates a parent menu labeled **FluentRead** and attaches two child items using the `browser.contextMenus.create` API. The restore option is initially disabled because no translation has occurred yet.

```typescript
// entrypoints/background.ts
browser.contextMenus.create({
    id: 'fluentread-parent',
    title: 'FluentRead',
    contexts: ['page', 'selection'],
});

browser.contextMenus.create({
    id: CONTEXT_MENU_IDS.TRANSLATE_FULL_PAGE,
    title: '全文翻译',
    parentId: 'fluentread-parent',
    contexts: ['page', 'selection'],
});

browser.contextMenus.create({
    id: CONTEXT_MENU_IDS.RESTORE_ORIGINAL,
    title: '撤销翻译',
    parentId: 'fluentread-parent',
    contexts: ['page', 'selection'],
    enabled: false, // initially disabled
});

```

The `contexts` array restricts these items to page and selection contexts, ensuring they appear when users right-click on web content rather than browser UI elements.

## Handling User Interactions

### Routing Clicks to Content Scripts

The background script listens for `browser.contextMenus.onClicked` events and routes them to the appropriate content script using `browser.tabs.sendMessage`. This decouples the UI layer from the translation logic.

```typescript
// entrypoints/background.ts
browser.contextMenus.onClicked.addListener((info, tab) => {
    if (!tab?.id) return;

    if (info.menuItemId === CONTEXT_MENU_IDS.TRANSLATE_FULL_PAGE) {
        browser.tabs.sendMessage(tab.id, {
            type: 'contextMenuTranslate',
            action: 'fullPage',
        });
        translationStateMap.set(tab.id, true);
        updateContextMenus(tab.id);
    } else if (info.menuItemId === CONTEXT_MENU_IDS.RESTORE_ORIGINAL) {
        browser.tabs.sendMessage(tab.id, {
            type: 'contextMenuTranslate',
            action: 'restore',
        });
        translationStateMap.set(tab.id, false);
        updateContextMenus(tab.id);
    }
});

```

### Managing Translation State Per Tab

To prevent users from translating already-translated pages or restoring untranslated ones, FluentRead maintains a `translationStateMap` that tracks whether each tab currently displays translated content. The `updateContextMenus` function synchronizes the menu UI with this state.

```typescript
// entrypoints/background.ts (excerpt)
const updateContextMenus = (tabId) => {
    const isTranslated = translationStateMap.get(tabId) || false;
    browser.contextMenus.update(CONTEXT_MENU_IDS.TRANSLATE_FULL_PAGE, {
        enabled: !isTranslated,
        title: isTranslated ? '全文翻译 (已翻译)' : '全文翻译',
    });
    browser.contextMenus.update(CONTEXT_MENU_IDS.RESTORE_ORIGINAL, {
        enabled: isTranslated,
        title: isTranslated ? '撤销翻译' : '撤销翻译 (无翻译)',
    });
};

```

This dynamic update provides immediate visual feedback: the translate option shows "(已翻译)" when disabled, and the restore option shows "(无翻译)" when no translation exists.

## Processing Translation Requests in content.ts

The content script listens for messages from the background script and delegates to the appropriate translation functions. It first checks whether the extension is enabled in the user's settings before executing any page modifications.

```typescript
// entrypoints/content.ts
if (message.type === 'contextMenuTranslate') {
    if (config.on === false) { 
        sendResponse({status: 'disabled'}); 
        return true; 
    }

    if (message.action === 'fullPage') {
        autoTranslateEnglishPage();          // ↔ translate whole page
        sendResponse({status: 'success', action: 'translated'});
        return true;
    } else if (message.action === 'restore') {
        restoreOriginalContent();            // ↔ revert to original
        sendResponse({status: 'success', action: 'restored'});
        return true;
    }
}

```

The `autoTranslateEnglishPage()` and `restoreOriginalContent()` functions, implemented in [`entrypoints/main/trans.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/main/trans.ts), handle the actual DOM manipulation and caching of original content.

## Synchronizing State Across Tab Lifecycle Events

To ensure menu states remain accurate when users switch tabs or navigate to new pages, the background script hooks into browser tab events. It clears translation flags when pages reload and removes stale entries when tabs close.

```typescript
// entrypoints/background.ts (excerpt)
browser.tabs.onActivated.addListener(info => updateContextMenus(info.tabId));
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
    if (changeInfo.status === 'complete') {
        translationStateMap.set(tabId, false);
        updateContextMenus(tabId);
    }
});
browser.tabs.onRemoved.addListener(tabId => translationStateMap.delete(tabId));

```

This lifecycle management prevents state leakage between navigation events and ensures that fresh pages always present the translate option as enabled.

## Summary

- **FluentRead** implements context menu integration for translations using the standard WebExtension `browser.contextMenus` API, creating a parent menu with two actionable child items.
- **State management** relies on a `translationStateMap` that tracks per-tab translation status, enabling dynamic UI updates that prevent invalid actions (translating already-translated pages).
- **Message passing** bridges the background script and content scripts, with the background handling UI events and the content script executing `autoTranslateEnglishPage()` or `restoreOriginalContent()`.
- **Lifecycle synchronization** ensures menu states reset correctly on tab switches, page reloads, and tab closures through listeners on `tabs.onActivated`, `tabs.onUpdated`, and `tabs.onRemoved`.

## Frequently Asked Questions

### How does FluentRead prevent users from translating a page twice?

FluentRead maintains a `translationStateMap` in [`entrypoints/background.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/background.ts) that records whether each tab currently displays translated content. When a user clicks "全文翻译", the background script sets the state to `true` and calls `updateContextMenus()`, which disables the translate option and enables the restore option. This state persists until the user clicks "撤销翻译" or navigates to a new page.

### Can I trigger FluentRead's translation programmatically without using the context menu?

Yes. You can send a message directly to the content script using the WebExtension messaging API. The background script listens for `contextMenuTranslate` messages with actions `fullPage` or `restore`, and you can replicate this from another extension or developer tools by calling `browser.tabs.sendMessage(tabId, {type: 'contextMenuTranslate', action: 'fullPage'})`.

### Why does the "撤销翻译" option appear disabled when I first install the extension?

The "撤销翻译" (Restore Original) menu item is initialized with `enabled: false` in [`entrypoints/background.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/background.ts) because no translation has occurred yet. The menu only becomes active after you successfully translate a page, which sets the tab's state in `translationStateMap` to `true` and triggers `updateContextMenus()` to enable the restore option while disabling the translate option.

### Where does FluentRead store the original page content before translation?

While the background script tracks translation state, the actual storage and restoration of original content occurs in the content script layer. When `autoTranslateEnglishPage()` is invoked (referenced in [`entrypoints/content.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/content.ts) and implemented in [`entrypoints/main/trans.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/main/trans.ts)), the extension caches the original DOM content before replacing it with translated text. Calling `restoreOriginalContent()` retrieves this cached data to revert the page.