# How FluentRead Differs from Other Bilingual Translation Extensions: Architecture Deep Dive

> Explore FluentRead's unique queue-driven, modular architecture. Discover its persistent local caching and DOM injection, setting it apart from traditional translation extensions.

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

---

**FluentRead implements bilingual translation through a queue-driven, modular architecture that isolates translation engines, implements persistent local caching, and uses MutationObserver-based DOM injection—features rarely found in traditional browser extensions that typically rely on hard-coded API calls and tooltip overlays.**

Most browser extensions handle bilingual translation through simple API calls and DOM overlays that break when pages update dynamically. The open-source **FluentRead** project (`bistutu/fluentread`) fundamentally reimagines how **bilingual translation extensions** should work, employing a sophisticated task queue system and modular service architecture that prioritizes performance, privacy, and extensibility.

## Modular Translation Engine Architecture

Unlike extensions that hard-code a single provider directly into UI scripts, FluentRead isolates each translation engine in dedicated service modules under `entrypoints/service/*.ts`. Each adapter—whether for Google, DeepL, Azure, or Claude—exposes a simple async function that the central router consumes.

The [`translateApi.ts`](https://github.com/bistutu/fluentread/blob/main/translateApi.ts) utility acts as the unified gateway, routing every request through [`translateQueue.ts`](https://github.com/bistutu/fluentread/blob/main/translateQueue.ts). This separation of concerns allows developers to add new translation providers without modifying core logic, simply by creating a new file in the service directory and registering it in [`entrypoints/service/_service.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/service/_service.ts).

## Intelligent Queue and Retry Management

Where typical bilingual translation extensions fire requests immediately and fail silently on network errors, FluentRead implements a robust task queue in [`entrypoints/utils/translateQueue.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/translateQueue.ts). This system stores pending translation tasks, limits concurrent API calls to prevent rate limiting, and automatically retries failed requests with exponential back-off configured via `TranslateOptions`.

The queue manages lifecycle events meticulously. When users navigate away from a page, `cancelAllTranslations` clears pending tasks to prevent unnecessary network traffic. This approach eliminates the "fire and forget" fragility common in simpler extensions.

## Persistent Local Caching

FluentRead's [`cache.ts`](https://github.com/bistutu/fluentread/blob/main/cache.ts) module stores both raw translation results and bilingual-rendered output in `chrome.storage.local`. When users re-visit content or re-select previously translated text, the extension retrieves results instantly without redundant API calls.

This contrasts sharply with most bilingual translation extensions that lack persistent caching, forcing repeated remote requests for identical snippets and increasing latency.

## DOM-Aware Bilingual Rendering

Traditional extensions overlay tooltips or replace original nodes, breaking page layouts and failing when content loads dynamically (infinite scroll, SPAs). FluentRead's `handleBilingualTranslation` function in [`entrypoints/main/trans.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/main/trans.ts) creates a wrapper node (`fluent-read-bilingual`) containing the original text alongside a `fluent-read-bilingual-content` element.

A **MutationObserver** watches the DOM continuously, ensuring newly loaded content receives automatic bilingual treatment without user intervention. This approach preserves the original document structure while supporting modern web applications that update content dynamically.

## Dual Interaction Modes: Slide vs. Hover

FluentRead distinguishes between **slide-translation** and **hover-translation** through the `slide` flag. Slide mode triggers on mouse movement across the page and removes bilingual nodes instantly to avoid visual clutter. Hover mode displays a persistent bilingual block when users pause over specific elements.

Most competing extensions support only a single hover-tooltip style. FluentRead's dual-mode system adapts to different reading workflows, configured through [`config.ts`](https://github.com/bistutu/fluentread/blob/main/config.ts) and [`option.ts`](https://github.com/bistutu/fluentread/blob/main/option.ts) alongside extensive UI customization options including hotkeys, debounce delays, and per-site bilingual toggles.

## Privacy-First Multi-Engine Fallback

FluentRead stores all translation results locally and never transmits user text to analytics endpoints—a critical differentiator from extensions that rely on cloud-hosted back-ends or telemetry. If the primary translation engine fails, the queue system can automatically re-queue requests with alternative services (Google → Azure → DeepL) without user intervention, ensuring translation continuity that single-engine extensions cannot match.

## Implementation Examples

### Triggering Bilingual Translation on Hover

```typescript
// entrypoints/main/trans.ts – core hover handler
import { handleTranslation } from '@/entrypoints/main/trans';

document.addEventListener('mousemove', (e) => {
  // delayTime of 300 ms gives enough time for a hover
  handleTranslation(e.clientX, e.clientY, 300);
});

```

### Programmatic Bilingual Translation

```typescript
import { handleBilingualTranslation } from '@/entrypoints/main/trans';

const node = document.querySelector('.article-content p');
if (node) {
  // `false` = hover mode (keeps the bilingual node visible)
  handleBilingualTranslation(node, false);
}

```

### Adding a Custom Translation Service

```typescript
// src/entrypoints/service/custom.ts
import { method } from '../utils/constant';
import { config } from '@/entrypoints/utils/config';

export default async function custom(message: any) {
  const resp = await fetch('https://my-translator/api', {
    method: method.POST,
    body: JSON.stringify({ text: message.origin, from: config.from, to: config.to })
  });
  const { translation } = await resp.json();
  return translation;
}

```

Register this service in [`entrypoints/service/_service.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/service/_service.ts) to integrate it into the routing system.

### Direct Queue Usage

```typescript
import { enqueueTranslation } from '@/entrypoints/utils/translateQueue';

enqueueTranslation(async () => {
  const result = await translateText('你好世界', 'Example Page');
  console.log('Result →', result);
});

```

## Summary

- **FluentRead** employs a **modular service architecture** isolating translation engines in `entrypoints/service/*.ts`, unlike monolithic extensions.
- A **centralized task queue** ([`translateQueue.ts`](https://github.com/bistutu/fluentread/blob/main/translateQueue.ts)) manages concurrency, retries, and cancellation, eliminating the fragile "fire and forget" approach of typical extensions.
- **Persistent local caching** via [`cache.ts`](https://github.com/bistutu/fluentread/blob/main/cache.ts) prevents redundant API calls for previously translated content.
- **MutationObserver-based rendering** in `handleBilingualTranslation` supports dynamic DOM updates without breaking page layouts.
- **Dual interaction modes** (slide vs. hover) and extensive customization through [`config.ts`](https://github.com/bistutu/fluentread/blob/main/config.ts) provide flexibility rare in bilingual translation extensions.
- **Privacy-first design** with local-only storage and automatic multi-engine fallback ensures reliability without compromising user data.

## Frequently Asked Questions

### How does FluentRead handle dynamic content loading on modern web apps?

FluentRead uses a **MutationObserver** in [`entrypoints/main/trans.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/main/trans.ts) to monitor DOM changes continuously. When new content appears (infinite scroll, AJAX updates), the observer triggers `handleBilingualTranslation` automatically. This differs from traditional extensions that target static pages and fail when content updates dynamically.

### Can I add my own translation API to FluentRead?

Yes. Create a new file in `entrypoints/service/` (e.g., [`custom.ts`](https://github.com/bistutu/fluentread/blob/main/custom.ts)) exporting an async function that accepts the message object and returns translated text. Register the service in [`entrypoints/service/_service.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/service/_service.ts), and [`translateApi.ts`](https://github.com/bistutu/fluentread/blob/main/translateApi.ts) will route requests to your implementation automatically, leveraging the existing queue and caching infrastructure.

### Why does FluentRead use a queue system for translations?

The queue in [`entrypoints/utils/translateQueue.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/translateQueue.ts) prevents rate limiting by controlling concurrent requests, implements exponential back-off for retries, and allows cancellation when users navigate away. Most bilingual translation extensions lack this layer, causing failed translations during network fluctuations and unnecessary API costs from duplicate requests.

### Is my translation data private when using FluentRead?

Yes. FluentRead stores all results in `chrome.storage.local` via [`cache.ts`](https://github.com/bistutu/fluentread/blob/main/cache.ts) and never transmits user text to remote analytics servers. The extension is fully open-source (available at `bistutu/fluentread`), allowing verification that translation data remains on your local device unlike cloud-dependent alternatives.