Euspell
Docs

Architecture

One conversion engine, compiled from source data, driven by many surfaces. Every tool is a thin layer around the same pure string functions.

Build pipeline

The lexicon is authored as CSVs and compiled to JavaScript maps; the content script is then bundled with Rollup. No TypeScript — JSDoc supplies the types.

data → dist → bundle
data/*.csv  →  compile-lexicon.js  →  dist/lexicon.js
                                    →  dist/abbreviations.js
                                    →  dist/contractions.js
                                    →  dist/phrases.js

src/content/*.js + dist/*.js  →  Rollup (iife)  →  dist/content-bundle.js

The engine is DOM-free

The conversion core is text-in / text-out. Each stage is a pure function, so it runs anywhere — a browser page, an Electron iframe, a Word taskpane, an Apps Script runtime, a test harness:

  • tokenize(text) — words, separators, and contractions (contraction-aware).
  • tagWord(word) — the lexical CLAWS7 candidate set from the lexicon.
  • convert(word, tokens, idx) — context-aware respelling with full disambiguation.
The one DOM-coupled piece
Only walkTextNodes(root, convert)touches the DOM. Drivers that have a DOM (the extension, Eupub) use it; drivers that don't (Word, LibreOffice, Google Docs, dictation) call convertText(text) directly. This is why the engine ports so cleanly across surfaces.

The convert flow

src/content/converter.js
function convert(word, tokens, idx) {
  const entry = lexicon.get(word.toLowerCase()) ?? contractions.get(word.toLowerCase());
  if (!entry) return word;

  const variants = entry.encoding % 10;      // euspelling count
  if (variants === 0) return word;           // unchanged
  if (variants === 1) return matchCase(word, entry.spellings[0]);

  const i = disambiguate(entry, tokens, idx); // pos.js or semantic/
  return matchCase(word, entry.spellings[i] ?? word);
}

Surfaces around the core

  • Chrome extension — content script + service worker; its own PDF.js viewer for PDFs.
  • Eupub reader — Electron; injects the engine bundle into each chapter iframe.
  • Office converters — Word (Office.js), LibreOffice (Python port), Google Docs (Apps Script port), each validated 35/35 against the reference engine.
  • Dictation — an additive authoring path feeding speech through convertText.
  • Derived lexicons — PLS for TTS, Morfologik / Harper for grammar checkers.

Testing

  • Unit tests — converter, DOM walker, matchCase, individual POS functions.
  • Corpus tests — CLAWS7-tagged sentences per ambiguous word, asserting the disambiguation output.
  • Cross-engine fixtures — the Python and Apps Script ports are checked against the JS engine.