File Structure for Translation Service Implementations in FluentRead: A Modular Architecture Guide

FluentRead organizes every translation engine as an isolated module under entrypoints/service/, with each engine exporting a single async function that handles API communication, while a central registry in _service.ts maps service identifiers to these implementations for dispatch by the background script.

FluentRead is an open-source browser extension that unifies multiple translation providers under a single interface. Understanding the file structure for translation service implementations in FluentRead is essential for contributors looking to add new engines or customize existing ones. The codebase follows a strict modular pattern where each translation service is self-contained, making the system highly extensible and maintainable.

Modular Architecture in entrypoints/service/

One File Per Translation Engine

Each translation provider lives in its own TypeScript file within entrypoints/service/. This isolation ensures that HTTP logic, authentication handling, and response parsing for each provider never interferes with others.

Key implementation files include:

Standardized Function Signature

Every engine module exports a default async function with a consistent signature. This standardization allows the central registry to invoke any service interchangeably.

// entrypoints/service/youdao.ts (example pattern)
export default async function youdao(message: any): Promise<string> {
  // Extract text and context from message
  const { origin, context } = message;
  
  // Perform API call with config from utils/config.ts
  const response = await fetch(apiUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ q: origin, from: config.from, to: config.to })
  });
  
  // Parse and return translated text
  const data = await response.json();
  return data.translation[0];
}

Central Service Registry in _service.ts

The entrypoints/service/_service.ts file acts as the central dispatcher. It imports all engine modules and exports a ServiceMap object that maps service identifiers to their implementations.

// entrypoints/service/_service.ts
import microsoft from './microsoft';
import youdao from './youdao';
import deepl from './deepl';
import google from './google';
// ... other imports

export const _service = {
  [services.microsoft]: microsoft,
  [services.youdao]: youdao,
  [services.deepl]: deepl,
  [services.google]: google,
  // ... other mappings
};

The services constants are defined in entrypoints/utils/option.ts, ensuring type safety across the codebase. This registry pattern allows the background script to look up and invoke the correct engine dynamically based on user configuration.

Request Flow from UI to Translation Engine

Understanding the file structure requires seeing how data flows through the system:

  1. UI Layer: Components call translateText() from entrypoints/utils/translateApi.ts
  2. Queue Management: translateApi.ts uses entrypoints/utils/translateQueue.ts to limit concurrent requests (default: 6 parallel translations)
  3. Background Script: The queued task sends a runtime.sendMessage to entrypoints/background.ts
  4. Service Dispatch: background.ts retrieves the current config.service value and executes _service[config.service](message)
  5. Engine Execution: The specific module in entrypoints/service/ performs the HTTP request and returns the translated string

Adding a New Translation Service

To implement a new provider, modify only two files:

Step 1: Create the engine file in entrypoints/service/

// entrypoints/service/libreTranslate.ts
import { config } from "@/entrypoints/utils/config";

export default async function libreTranslate(message: any): Promise<string> {
  const url = config.proxy[config.service] || "https://libretranslate.com/translate";
  
  const resp = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      q: message.origin,
      source: config.from === "auto" ? "auto" : config.from,
      target: config.to,
      format: "html"
    })
  });
  
  if (!resp.ok) {
    throw new Error(`LibreTranslate failed: ${resp.status}`);
  }
  
  const result = await resp.json();
  return result.translatedText;
}

Step 2: Register in entrypoints/service/_service.ts

import libreTranslate from "./libreTranslate";

export const _service = {
  // ... existing services
  [services.libreTranslate]: libreTranslate,
};

Step 3 (Optional): Add the identifier to entrypoints/utils/option.ts if not already present.

Key Files in the Translation Architecture

File Purpose
entrypoints/service/ Directory containing one module per translation engine (e.g., microsoft.ts, youdao.ts, deepl.ts)
entrypoints/service/_service.ts Central registry mapping service identifiers to implementation functions
entrypoints/utils/option.ts Defines services constants and helper predicates (isMachine, isAI)
entrypoints/utils/translateApi.ts Public API for UI components; handles caching, retries, and queueing
entrypoints/utils/translateQueue.ts Concurrency controller limiting parallel translation requests
entrypoints/background.ts Message broker that dispatches requests to the appropriate service module
entrypoints/utils/config.ts Runtime configuration store (selected service, API keys, language pairs)

Summary

  • FluentRead uses a modular file structure where each translation service is isolated in its own file under entrypoints/service/.
  • The _service.ts registry acts as a central dispatcher, mapping service identifiers to engine implementations.
  • Adding a new provider requires creating a single file in entrypoints/service/ and registering it in _service.ts.
  • The architecture separates concerns: UI components use translateApi.ts, queue management handles concurrency, and background.ts routes requests to the specific engine.
  • This design makes the codebase extensible, testable, and maintainable for contributors adding new translation providers.

Frequently Asked Questions

Where are translation services implemented in FluentRead?

All translation engines are implemented as individual TypeScript modules in the entrypoints/service/ directory. Each file (such as microsoft.ts, youdao.ts, or deepl.ts) exports a single async function that handles the HTTP communication with that specific provider's API.

How do I add a new translation service to FluentRead?

To add a new service, create a new file in entrypoints/service/ that exports a default async function receiving a message object and returning a translated string. Then import and register this function in entrypoints/service/_service.ts by adding it to the _service map using the appropriate service identifier from utils/option.ts.

What is the role of _service.ts in FluentRead's translation architecture?

The entrypoints/service/_service.ts file serves as the central registry that maps service identifiers (like services.microsoft or services.youdao) to their corresponding implementation functions. When the background script receives a translation request, it uses this map to dispatch the request to the correct engine module based on the user's current configuration.

How does FluentRead handle concurrent translation requests?

FluentRead implements a concurrency controller in entrypoints/utils/translateQueue.ts that limits the number of simultaneous translation requests (defaulting to 6 parallel translations). When UI components call translateText() from entrypoints/utils/translateApi.ts, requests are enqueued and processed according to this limit, preventing rate limiting issues with external APIs and managing browser resource usage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →