Experimental project

This is a sandbox project, which contains experimental code for developer use only.

Status: pre-alpha sandbox project. APIs unstable. Active
development.

The module applies Drupal's well-known interface-translation pattern (the
locale module) to content fields. A source string is
extracted once, deduplicated by hash, translated once per target language, and
substituted at render time. The source entity is never duplicated.

Features

  • Pluggable backend. Ships with an OpenAI-compatible adapter
    that works with Ollama (local), Azure OpenAI, RunPod, Modal, vLLM, LM Studio,
    Together AI, Groq, and any service exposing the OpenAI Chat Completions API.
    Write your own adapter in ~150 lines of PHP.
  • Translation memory by default. The string "Read more" is
    translated once and reused across every node and paragraph that contains it. You
    pay the backend once, not 500 times.
  • No entity duplication. Your source content stays as a
    single English entity. The French version of a page is the same entity rendered
    with translated strings. Views, listings, Solr, and migrations are
    untouched.
  • One backend, two surfaces. The same backend that
    translates your content also translates Drupal interface strings (menus,
    t() calls, config) via the existing locale storage. One config, one
    prompt template, one glossary serves both.
  • Status tracking and retry. Per-language status enum
    (untranslated, queued, in-flight, translated, outdated, manual override, failed)
    with automatic retry on transient errors and editor-driven retry on
    failures.
  • Editor sidebar widget. Per-entity translation progress
    visible on the node edit form with one-click retry.
  • Admin UI at
    /admin/config/regional/translate/content extending the same UX as
    Drupal's interface-translation admin page.
  • PO file import/export for handoff to external translators
    using standard gettext tooling.
  • Per-language URL aliases via Pathauto and a custom token,
    with redirect-module integration for graceful slug transitions when translations
    update.
  • Drush commands for backfill, retry, status reporting, and
    bulk formatter migration.

Post-Installation

  1. Enable the language core module and configure URL language
    negotiation (prefix mode) at
    /admin/config/regional/language/detection.
  2. Add the target languages you want at
    /admin/config/regional/language.
  3. Install a backend submodule (e.g.
    content_locale_openai_compatible) and configure it at
    /admin/config/regional/translate/content/backend — endpoint URL,
    model, API key (via the Key module recommended), prompt template, and
    glossary.
  4. Flip the field formatters on the content types you want translatable:
    drush content_locale:flip-formatters --bundle=article
      --display=default
  5. Backfill existing content:
    drush
      content_locale:extract-all
  6. Monitor progress at /admin/config/regional/translate/content
    or via the sidebar widget on any entity edit form.

Importantly: do not enable the
content_translation core module — Content Locale is a deliberate
alternative that's incompatible with the per-entity translation approach.

Additional Requirements

  • Drupal core ^11
  • PHP 8.3 or later
  • The language and locale core modules
  • A backend submodule (one ships with the project; you can write your
    own)
  • Pathauto — required
    for per-language URL aliases via the custom token.
  • Key — store backend API
    credentials outside of configuration.
  • Redirect — automatic
    redirects when translation-driven alias changes update slugs.
  • Simple XML
    Sitemap
    — per-language XML sitemap generation.

Similar projects

  • Content Translation (Drupal core). Duplicates the source
    entity per language with separate revisions, fields, and translations. Forces
    Solr per-language indexes and Views/listings rework. Content Locale instead keeps
    a single entity and substitutes translations at render time.
  • TMGMT (Translation
    Management Tool)
    .
    Provides a job-based workflow over Drupal's
    per-entity content translation. Content Locale is deliberately job-less — strings
    flow directly from extraction to backend to render — and works at the string
    level rather than the entity level.
  • Locale (Drupal core). Same pattern as Content Locale, but
    for code-level t() strings only. Content Locale extends that pattern
    to content fields and shares the backend so both surfaces translate through the
    same service.

Community Documentation

Architecture and design captured in the project's OpenSpec change proposal
(link forthcoming once the sandbox is published). Issue queue is the canonical
place for questions during pre-alpha.

Project information