Skip to content

Structural diff hook for translation quality — experience report & suggestion #3

Description

@Alfystar

Hi @DavidLMS 😁,

First of all, thank you for building Aphra. We've been using it in LibreFolio to automatically translate our MkDocs documentation (36 source files × 3 languages = 108 translated files), and the results have been genuinely impressive.

This issue isn't about a bug — it's about sharing what we built on top of Aphra and a suggestion that might benefit other users.

Our setup

We installed Aphra directly via Pipenv from your GitHub repository:

[dev-packages]
"aphra " = {git = "https://github.com/DavidLMS/aphra.git"}

This worked perfectly. You might consider mentioning Pipenv as an alternative installation method alongside pip/Poetry in your README — it's already supported out of the box since you have a proper setup.py/pyproject.toml.

What we customized

We wrapped Aphra's workflow (Analyze → Translate → Critique → Refine) inside an orchestration script (translate_docs.py) that adds:

  1. Shared analysis — Step 1 (Analyze) runs once per source file and its result is reused across all 3 target languages. This saves ~25% of LLM calls.

  2. Step 2 (Search) paused — We disabled the web search step entirely (APHRA_WEB_SEARCH=false). For technical documentation where terms are well-known, it added cost and latency without benefit.

  3. Structural diff (our main addition) — Instead of relying solely on the LLM critic to catch structural issues, we created an objective, deterministic structural diff function (_structural_diff()) that compares the source and translated markdown on 13 dimensions:

    • Heading count and level sequence
    • Code block count and language tags
    • Link URLs (missing/added)
    • Image URLs
    • MkDocs admonition count (!!!, ???)
    • Horizontal rules, bullet lists, numbered lists
    • Table row count
    • Bold marker count
    • Line count delta

    The diff report is injected into the glossary parameter of the Critique step, so the critic LLM receives factual, objective data alongside its own subjective analysis. This dramatically reduced false negatives — issues the critic would have missed on its own.

  4. Post-translation validation — A separate script (validate_translations.py) runs offline checks on all translated files (admonition indentation, artifact remnants like [N] glossary markers, truncated words, LaTeX corruption, etc.).

Suggestion: custom diff hook parameter

We think other Aphra users could benefit from a hook/callback parameter in the pipeline — something like:

def my_structural_diff(source: str, translated: str) -> str:
    """Return a factual report to inject into the critique step."""
    ...

aphra.translate(
    source_text,
    ...,
    structural_diff_fn=my_structural_diff,  # optional callback
)

This way, users could provide their own domain-specific quality checks (markdown structure for us, XML tags for others, JSON schema for i18n files, etc.) without forking the library.

Results

The Markdown structural diff is working well. We did have to clean up some "Translator's Notes" that the LLM occasionally appended, but overall the quality has been excellent — especially with cloud models (we used stepfun/step-3.5-flash via OpenRouter for the final run, $0 cost on their free tier).

We're also considering building a JSON i18n translation pipeline next (for our svelte-i18n frontend localization files), which would be a different beast but could reuse the same Aphra workflow with a JSON-aware structural diff.

Thank you

We wanted to share this experience not because there were problems, but in the hope that these ideas might help you improve the project. The Analyze → Critique → Refine architecture is really well thought out, and adding an objective diff layer on top of it made it even more powerful for our use case.

All our customization code is public in the LibreFolio repository — feel free to look at it, reuse it, or draw inspiration from it.

Keep up the great work! 🙏

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions