Paragraphs
Architecture

How Paragraphs works.

A content-addressable translation graph that lives above your CMS. The whole pipeline, in five steps, with the data structure exposed.

Mental model

The translation problem, rethought.

Old way

Duplicate posts per locale

  • · Every translation is a new post row, linked by meta
  • · Your URL structure is dictated by the plugin's schema
  • · Editing English requires re-syncing 12 locale clones
  • · Migrate off the plugin and your translations die
  • · Admin gets slower with every added locale
Paragraphs

One source, one graph

  • · Source content fingerprinted on save
  • · Translations live in an external graph keyed by fingerprint
  • · URLs are yours: subdir, subdomain, TLD — your choice
  • · Switch CMS in 2027 — the graph still serves your site
  • · Edit one paragraph, only that paragraph re-translates
The pipeline

What happens between save and serve.

  1. 01

    Install

    WordPress plugin, Next.js adapter, Astro integration, Sanity plugin, or raw API. One config per project.

  2. 02

    Author

    Write a post, ship a product, edit a docs page. Your editor stays exactly as it was.

  3. 03

    Fingerprint

    Paragraphs hashes every translatable unit on save. blake3(unit_type || content). Same content, same fingerprint.

  4. 04

    Translate

    The cascade picks the right model per unit type. Quality-scored. Glossary-enforced. Memorised.

  5. 05

    Serve

    Cloudflare Worker substitutes at the edge — or write to messages/ at build time. Your call per project.

01
Source
WP, Sanity, MDX, JSON
02
Fingerprint
blake3(unit_type || content)
03
Graph
source_units · translations · TM · glossary
04
Cascade
DeepL · Claude · TM lookup
05
Delivery
Edge Worker · build-time · hybrid
Markers

What a fingerprint looks like in practice.

Every translatable unit gets a 12-character fingerprint derived from its content. Whether it's a button label, a paragraph, a product description, or a JSON-LD value — the marker is how the edge knows what to substitute.

Source unit · fingerprint A3F5C9E27K2P
<button data-pg="A3F5C9E27K2P">Add to cart</button>
→ rendered as Añadir al carrito at /es/
Data structure

What lives in the graph.

source_units sql
CREATE TABLE source_units (
  id              uuid PRIMARY KEY,
  project_id      uuid NOT NULL,
  unit_type       text NOT NULL,          -- 'paragraph' | 'button' | ...
  fingerprint     text NOT NULL,          -- blake3(unit_type || content)
  content         text NOT NULL,
  embedding       vector(3072),           -- for translation memory
  created_at      timestamptz NOT NULL,
  UNIQUE (project_id, fingerprint)
);
translations sql
CREATE TABLE translations (
  id              uuid PRIMARY KEY,
  source_unit_id  uuid NOT NULL REFERENCES source_units(id),
  locale          text NOT NULL,
  content         text NOT NULL,
  model           text NOT NULL,          -- 'deepl' | 'claude-sonnet' | ...
  quality_score   numeric(3,2),
  status          text NOT NULL,          -- 'pending' | 'approved' | ...
  branch_id       uuid,
  created_at      timestamptz NOT NULL
);

Glossary terms, translation memory matches, and branch state extend the same graph. Translations are first-class rows — versioned, queryable, exportable. Full data model →

Delivery

Build-time, edge runtime, or hybrid.

Mode Best for Trade-off
Build-time Static sites (Next.js / Astro / Nuxt) with stable content Re-build on translation update; zero runtime cost
Edge runtime WordPress, dynamic apps, frequent content updates Cloudflare Worker substitutes in <40ms p99
Hybrid Marketing pages static, app pages dynamic Configurable per-route in the dashboard
Two big consequences

Why the graph design matters.

Edit one paragraph, only that paragraph re-translates.

Change a single block in a 50-block post and only the fingerprint for that block goes through the cascade. The other 49 stay cached. Translation costs scale with what you change, not what you have.

Move CMS, your translations come with you.

Switch from WordPress to Sanity in 2027? The same fingerprints still match, the same translations still serve. You don't pay to re-translate when you migrate platforms.

See the contrast with WPML's duplicate-posts model, or how it composes with next-intl.

Want to see it on a real site?

Free tier covers a small production site — 100,000 words / month.