Dual-line webpage translation for desktop Chrome and Edge. Preserve source text, render translations underneath, highlight model alignments on hover, and collect aligned vocabulary into a standalone records workspace.
Chinese documentation · Quick Start · Configuration · Testing · Contributing
Status: active prototype. The extension is useful for local development and unpacked-extension testing. Public distribution still needs an explicit
LICENSEfile, release notes or changelog, and a final privacy review.
- Project Status
- Highlights
- Feature Scope
- Quick Start
- Configuration
- Usage
- Architecture
- Development
- Testing Matrix
- Packaging And Releases
- Privacy And Security
- Contributing
- Roadmap
- License
metatranslation is a Chromium Manifest V3 extension focused on reading workflows where the original page still matters. It does not replace source text. Instead, it injects translated lines beside the original reading flow, uses model-returned source-span alignment for hover highlighting, and records vocabulary from stable source-side hovers or explicitly selected aligned source text.
Current release posture:
- Target browsers: desktop Chrome and Edge with Manifest V3.
- Install mode: local unpacked extension from the generated
distdirectory. - API shape: OpenAI-compatible
chat/completions. - Alignment policy: model-only alignment. Invalid blocks are retried and then skipped instead of guessed.
- Release readiness: packaging and GitHub Release automation exist, but public distribution is blocked on license, release notes, and privacy review.
- Preserves original webpage text and injects translations as the next visual line.
- Translates progressively with concurrent chunk requests; completed chunks render immediately.
- Validates translated-part model output before using hover alignments.
- Keeps webpage text, adjacent context, and page URLs framed as untrusted data in provider prompts.
- Supports source and target hover highlighting, source-hover dictionary lookup, vocabulary recording, a local activity dashboard, and a standalone records workspace.
- Localizes the manifest, context menu, options page, in-page diagnostics, and dictionary popup in English and Simplified Chinese through Chrome i18n.
- Stores settings and bounded Test Mode logs in
chrome.storage.local; stores cache, records, and local usage stats in IndexedDB. - Exports and imports portable settings JSON from the options page for version upgrades, profile moves, and unpacked-extension id transitions.
- Provides optional Test Mode logs for local troubleshooting, with redaction, bounded retention, and JSON export from the options page.
- Provides focused unit checks, browser smoke tests, mock-provider E2E, real-provider E2E, and package automation.
| Area | Current behavior |
|---|---|
| Activation | Manual toggle through extension action or page context menu. No default all-site auto-translation. |
| Extraction | Conservative top-to-bottom TreeWalker discovery for headings, paragraphs, list items, links, buttons, and supported input buttons. |
| Rendering | Source DOM text remains in place. Translation nodes inherit source text style and are removable on disable. Dense flex/grid and overlay labels use internal second-line rendering. |
| Translation | OpenAI-compatible provider calls with structured JSON schema output, configurable target language, context window, concurrency, chunk size, timeout, and retry count. |
| Alignment | Prompt blocks include markedText span-id guides, sourceSpans, and a kind/action/policy/constraint-oriented alignmentPlan.sourceWorklist; the model returns translatedParts[].sourceSpanIds, and the extension derives runtime source and target ranges locally. Strict and tolerant validation modes are supported. |
| Dictionary | Source-hover dictionary popup can use WiktApi, FreeDictionaryAPI, or be turned off. Source-side lookup uses the locally segmented source word under the pointer, even when model alignment groups multiple source spans. Dictionary results are normalized and cached locally. |
| Records | Stable 2-second source-side hover or aligned source selection records vocabulary. Counts dedupe the same normalized word or phrase in the same source sentence. |
| Records UI | Options page dashboard with launch count, carousel cards for processed sentence/source-word trends and per-module activity heatmaps, hover details, records entry, and CSV export; standalone records page with word/sentence cards, sorting, pagination, details, and lazy dictionary lookup. |
| Export | Options page CSV export includes a UTF-8 BOM and neutralizes spreadsheet formula prefixes from untrusted page text. |
| Settings portability | Options page settings export/import writes a JSON file containing provider settings, model, target language, runtime knobs, dictionary settings, and Test Mode. |
| Diagnostics | In-page status panel plus background diagnostics for skipped blocks, failed chunks, provider-output failure categories, alignment coverage, and optional Test Mode event logs. |
| UI Language | Extension UI follows the browser UI language. English is the default locale and Simplified Chinese is supported; this is independent of Target Language. |
Unsupported in the current scope:
- Firefox and Safari.
- Default all-site automatic translation.
- Local heuristic alignment fallback.
- Public store distribution packaging.
- Node.js
20.19+or22.12+. - npm.
- Desktop Chrome, Edge, Chromium, or Chrome for Testing.
- An OpenAI-compatible translation provider key.
npm install --cache .npm-cachenpm run build- Open
chrome://extensions. - Enable
Developer mode. - Click
Load unpacked. - Select this repository's
distdirectory. - Open the extension options page.
- Configure
Base URL,API Key,Model, andTarget Language. - Open a normal
httporhttpspage. - Click the extension action button or page context-menu item to translate the page.
The extension calls an OpenAI-compatible chat/completions endpoint. A provider root URL such as https://openrouter.ai/api/v1 is accepted; trailing slashes are trimmed and /chat/completions is appended unless already present.
For a local Ollama model, use http://127.0.0.1:11434 or http://127.0.0.1:11434/v1, any non-empty placeholder API key such as ollama, and an installed model such as qwen2.5:0.5b. The default local Ollama root is resolved to /v1/chat/completions. Chrome extension requests normally include a chrome-extension://... Origin that default Ollama rejects; the extension installs a best-effort session-scoped request-header rule that removes Origin only from background requests to local Ollama on port 11434. Remote providers such as OpenRouter are not matched by this rule. If you expose Ollama on a non-default host or port, configure Ollama's OLLAMA_ORIGINS instead.
The extension UI language follows Chrome or Edge's UI language through _locales. Changing UI language does not change Target Language; that setting still controls the translation output sent to the provider prompt.
Use Export Settings on the options page before replacing an unpacked install, moving Chrome profiles, or testing a build that may get a different extension id. Use Import Settings after loading the new build to restore the API key, model, target language, and runtime settings. The exported JSON includes the API key, so keep it private and do not commit it. The packaged manifest includes a stable public key so future unpacked version folders keep a consistent extension id.
| Option | Default | Notes |
|---|---|---|
Base URL |
https://openrouter.ai/api/v1 |
OpenAI-compatible provider root or full chat completions URL. |
API Key |
empty | Stored in chrome.storage.local; paste only the token, not a full Bearer ... header. Common Bearer prefixes are stripped on save. Never commit real keys. |
Model |
x-ai/grok-4.3 |
Model used for translation and source-span alignment. |
Target Language |
zh-CN |
Injected into the provider prompt as the authoritative target language. |
Timeout (ms) |
30000 |
Per-request timeout. |
Request Chunk Size |
1 |
Number of text blocks per provider request. |
Parallel Requests |
64 |
Maximum concurrent content-runtime translation requests. |
Context Window Chars |
100 |
Adjacent source context sent for disambiguation; 0 disables adjacent context. |
Retry Count |
2 |
Retries after the first failed or invalid model call; 0 disables retry passes. |
Tolerant Provider Output |
on | Keeps valid text when imperfect model JSON can be safely recovered. Turn it off for strict provider-contract debugging. |
Dictionary Provider |
WiktApi |
WiktApi, FreeDictionaryAPI, or Off. |
Dictionary Hover Hold (ms) |
1000 |
Keep-alive window while moving from source text to the dictionary popup; 0 closes immediately. |
Test Mode |
off | Writes bounded local troubleshooting logs for background and content-runtime events. API keys and auth tokens are redacted. |
Provider request details:
- Sends
response_format: { type: "json_schema", json_schema: ... }. - Adds model-readable JSON schema field descriptions and minimum sizes for exact Payload id copying, non-empty target parts, target-part order, and same-block source-span references.
- Adds an
alignmentPlan.sourceWorklistto each prompt block so the model can process source spans as ordered kind/action/policy rows with direct execution constraints before the final checklist pass. - Sends
reasoning: { "effort": "none" }by default. - Does not send
reasoning_split. - Keeps OpenRouter-specific headers isolated in provider-header logic.
- Treats webpage text,
markedTextspan guides, adjacent context, and page URL as untrusted data in the provider prompt. - Uses three multilingual format-only alignment examples that omit output ids so models do not copy example ids.
- Removes
Originonly for extension background requests to local Ollama's defaulthttp://localhost:11434family of endpoints. - Resolves the default local Ollama root to
/v1/chat/completions; other provider roots still append/chat/completions. - Reports fine-grained provider-output failure counts plus aggregate alignment coverage in background diagnostics.
- Trigger translation manually with the action button or page context menu.
- Trigger again on the same document to disable and remove injected nodes.
- Navigate to a new document and trigger translation again; full-page navigation resets the previous page state.
- Hover a source span or translated span to highlight the aligned counterpart.
- Hover a source span for 2 seconds to record vocabulary when a valid alignment exists.
- Select aligned source text on a translated page and use the page context menu to add that word or phrase.
- Hover a source span to open the dictionary popup when dictionary lookup is enabled. The target highlight still follows the model alignment for that word when one exists.
- Use the options page to review local activity metrics, switch the carousel cards for the cumulative processed sentence/source-word chart and per-module 365-day activity heatmaps, hover chart or heatmap cells for daily details, open the standalone records page, or export vocabulary records as CSV.
- Use the options page to export settings JSON before replacing the extension folder, then import it after loading the new version if Chrome did not preserve
chrome.storage.local. - Use the records page to switch between word and sentence cards, search, sort, paginate, resize cards, and inspect details.
- Enable
Test Modeon the options page while reproducing an issue, then refresh or export the Test Logs panel as JSON. - Use the browser UI language to switch extension UI between English and Simplified Chinese. Keep
Target Languagefor translation output only. - If translation appears to do nothing, check the bottom-right diagnostic panel. No panel means the runtime did not inject; an error panel usually means provider failure; high skipped counts mean invalid or empty model output.
Chrome action / context menu
|
v
background service worker
- settings
- provider calls
- translation cache
- dictionary lookup
- vocabulary records
- local usage stats
- test logs
|
v
content runtime
- TreeWalker extraction
- visible-text and page-chrome filtering
- MutationObserver dirty tracking
- translation rendering
- hover/highlight mapping
- source-hover record timer
|
v
options page
- local activity dashboard
- provider settings
- settings JSON import/export
- runtime tuning
- records page entry
- records CSV export
- test log view/export/clear
records page
- overview metrics
- word/sentence cards
- details and dictionary lookup
Key paths:
manifest.config.ts: MV3 manifest definition.public/_locales/*/messages.json: English and Simplified Chinese UI strings for manifest, context menus, options, diagnostics, and dictionary popup.docs/assets/metatranslation-header.png: README header image.src/background/index.ts: service worker, action/context-menu handling, message routing, cache orchestration, records entrypoints.src/background/openai.ts: OpenAI-compatible request builder, JSON extraction, retries, output validation.src/background/localOllama.ts: shared local Ollama URL detection and default endpoint resolution helpers.src/background/localOllamaCors.ts: local Ollama request-header rule scoping for extension Origin compatibility.src/background/dictionary.ts: WiktApi and FreeDictionaryAPI lookup normalization.src/background/db.ts: IndexedDB stores for translation cache, dictionary cache, daily usage stats, word records, sentence records, and word events.src/background/testLogs.ts: bounded local Test Mode log storage, redaction, query, and clear helpers.src/background/db.ts: IndexedDB stores for translation cache, dictionary cache, daily usage stats, word records, sentence records, and word events.src/content/injected.ts: injected runtime, DOM extraction, mutation tracking, rendering, hover mapping, highlight overlay, record timer.src/lib/alignment.ts: alignment sanitization and validation.src/lib/sourceSpans.ts: source-span generation for provider prompts and marked-text alignment guides.src/lib/settings.ts: settings normalization.src/lib/settingsFile.ts: portable settings JSON export/import helpers.src/options/main.ts: options UI, records page entry, and CSV export.src/records/main.ts: standalone records page behavior.src/lib/records.ts: record normalization, dedupe keys, heatmap, sorting, and pagination helpers.src/lib/stats.ts: usage-stat word estimation, daily stat merging, cumulative trends, and per-module activity heatmap data.src/lib/i18n.ts: shared options/background helper for Chrome i18n message lookup.scripts/: build, packaging, unit, smoke, mock E2E, real E2E, and live-page smoke helpers.docs/TECHNICAL_PLAN.md: current technical route, validation status, risks, and next steps.AGENTS.md: coding-agent workflow and project rules.
Use the repository scripts and local dependencies. Avoid ad hoc global tooling when a project script exists. The TypeScript build runs with strict unused-code checks. package.json keeps an npm override for rollup@2.80.0 because @crxjs/vite-plugin@2.4.0 depends on an older vulnerable Rollup 2 build.
npm install --cache .npm-cache
npm run test:unit
npm run build
npm testCommon commands:
| Command | Purpose |
|---|---|
npm run build |
Type-check and build the extension into dist. |
npm run test:unit |
Run focused module checks. |
npm test |
Run unit checks and build. |
npm run smoke:test |
Load the built extension in a browser and verify basic registration/UI. |
npm run e2e:mock |
Run browser E2E with a local mock provider. |
npm run e2e:layout |
Run the local layout matrix E2E for visible-text extraction and translation insertion. |
npm run e2e:page |
Run a real-page smoke test with mock or real provider, including source/target samples and runtime progress diagnostics. |
npm run e2e:pages |
Run the curated live-page suite across diverse public websites; real-provider mode defaults to smaller request chunks for alignment quality. |
npm run e2e:real |
Run fixture-based E2E against a configured real provider, including direct probe alignment-quality checks. |
npm run package:zip |
Rebuild and produce artifacts/metatranslation-extension-<version>.zip. |
| Layer | Command | Purpose |
|---|---|---|
| Focused unit checks | npm run test:unit |
Alignment validation, provider schema, prompt contract, dictionary parsing, settings normalization, diagnostics, Test Mode log sanitization, CSV escaping, record helpers, local usage-stat helpers, browser launch args, and i18n locale completeness. |
| Type-check and build | npm run build |
Confirms TypeScript and Vite can build the MV3 extension into dist. |
| Combined local validation | npm test |
Runs focused checks and build in one command. |
| Dependency audit | npm audit --cache .npm-cache |
Checks installed dependency vulnerability status using the local npm cache. |
| Browser smoke | BROWSER_BIN="/path/to/Chromium-or-Chrome-for-Testing" npm run smoke:test |
Loads the built extension and verifies basic registration/UI. |
| Mock-provider E2E | BROWSER_BIN="/path/to/Chrome for Testing" npm run e2e:mock |
Exercises extension behavior without real API quota. |
| Layout matrix E2E | BROWSER_BIN="/path/to/Chrome for Testing" npm run e2e:layout |
Exercises visible-text filtering, style copying, insertion mode, table-safe DOM placement, and grouped source-span hover recording across local fixture layouts. |
| Live-page smoke | BROWSER_BIN="/path/to/Chrome for Testing" PAGE_SMOKE_URL="https://example.com" npm run e2e:page |
Runs the extension against a live page with mock or real provider settings, reports source/target samples, runtime progress, quality flags, and layout diagnostics. |
| Curated live-page suite | BROWSER_BIN="/path/to/Chrome for Testing" npm run e2e:pages |
Runs the live-page smoke flow against MDN, GitHub Docs, GOV.UK, Hacker News, web.dev, Wikipedia, Cloudflare Learning, and Apple Mac. In real-provider mode it defaults to PAGE_SMOKE_REQUEST_CHUNK_SIZE=1 and PAGE_SMOKE_REQUEST_CONCURRENCY=16; override with PAGE_SUITE_REQUEST_CHUNK_SIZE and PAGE_SUITE_REQUEST_CONCURRENCY when needed. |
| Real-provider fixture E2E | BROWSER_BIN="/path/to/Chrome for Testing" REAL_TEST_BASE_URL="https://provider.example/v1" REAL_TEST_KEY="..." REAL_TEST_MODEL="model-id" npm run e2e:real |
Checks the configured provider through the extension background translation path, including direct and batch probe alignment coverage. Optional knobs include REAL_TEST_REQUEST_CHUNK_SIZE, REAL_TEST_REQUEST_CONCURRENCY, REAL_TEST_CONTEXT_WINDOW_CHARS, REAL_TEST_DICTIONARY_PROVIDER, and REAL_TEST_SCREENSHOT_TIMEOUT_MS. |
Automation note: use Chromium or Chrome for Testing. Some branded Google Chrome builds reject --load-extension in automated contexts. Browser E2E scripts read ignored local .env values when present, without overriding already exported environment variables, so real-provider keys can stay out of shell commands and commits. The scripts launch Chrome with --use-mock-keychain on macOS so Chrome for Testing does not ask for the Chromium Safe Storage keychain item, with --start-minimized by default, and with a best-effort CDP minimize pass after opening test targets so windows do not take focus. They avoid DevTools Page.bringToFront unless BROWSER_BRING_TO_FRONT=1 is set. e2e:real skips optional screenshots while the browser is minimized and reports that in screenshotResults; set BROWSER_START_MINIMIZED=0 and BROWSER_BRING_TO_FRONT=1 when you need an interactive visible window or screenshot artifacts.
npm run package:zipThe package script rebuilds the extension and writes artifacts/metatranslation-extension-<version>.zip. Keep generated archives out of git.
Pushing to main with a changed package.json version runs the Release on package version change workflow. The workflow compares the previous and current package versions; when the version changes, it installs dependencies with npm ci, verifies the root package-lock.json version, runs npm run test:unit, runs npm run package:zip, creates tag v<version>, and publishes a GitHub Release with artifacts/metatranslation-extension-<version>.zip.
For manual installation from GitHub Releases, download the attached metatranslation-extension-<version>.zip asset, unzip it, and choose the extracted folder in Chrome or Edge's "Load unpacked" dialog. Do not use GitHub's automatic "Source code (zip)" or "Source code (tar.gz)" archives; those are repository source snapshots and do not contain a root manifest.json.
If package.json changes but the version value is unchanged, the workflow exits without publishing. When preparing a release version, keep package-lock.json synchronized and ensure no existing v<version> tag already exists.
- Page text selected for translation is sent to the configured provider.
- API keys are stored in
chrome.storage.local. - Exported settings JSON contains the API key and should be treated as a secret.
- Translation cache, dictionary cache, daily usage stats, word records, sentence records, and word events are stored locally in IndexedDB.
- Test Mode logs are stored locally in
chrome.storage.localonly while enabled, retain a bounded recent history, and redact API keys, authorization headers, tokens, secrets, and passwords. They may include page URLs, event metadata, diagnostics, hovered or recorded words, the full provider request body, the full provider response body, and the extracted model message text so provider-output problems can be investigated. - The extension does not intentionally send records or cache contents to any service other than the configured translation provider and dictionary providers.
- Dictionary lookup sends the hovered source word plus language metadata to the selected dictionary provider.
- The
declarativeNetRequestWithHostAccesspermission is used only to remove theOriginrequest header from extension background requests to local Ollama on port11434. - CSV export contains webpage text and URLs; review before sharing.
- Do not commit real API keys, screenshots containing private pages, browser profiles, or generated artifacts.
Before opening a change:
- Read AGENTS.md for repository conventions.
- Keep English Markdown documents and matching
*_cn.mdtranslations synchronized. - Update docs/TECHNICAL_PLAN.md when behavior, validation status, risks, or release posture changes.
- Use focused module tests for logic changes and browser tests for extension behavior changes.
- Run at least
npm run test:unitandnpm run build. - Do not introduce provider-specific behavior unless the tradeoff has been discussed.
- Do not add heuristic local alignment fallback unless explicitly requested.
- Bump
package.jsonandpackage-lock.jsontogether when preparing a GitHub Release. - Keep generated files such as
dist/,artifacts/,.npm-cache/, screenshots, and profiles untracked.
- Broaden browser fixtures for content-runtime extraction and insertion boundaries.
- Add optional debug export for skipped blocks and invalid provider output.
- Add filtering and search controls for the Test Logs panel.
- Improve release metadata before publishing, including a license file, changelog, and privacy policy.
- Re-run real-provider E2E after prompt or output-contract changes.
- Continue tuning page-layout handling through real-page smoke tests instead of site-specific hacks.
No license file is currently included. Add an explicit LICENSE file before public open-source distribution.
