Create dual-language EPUB books with OpenRouter translations.
Project description
EPUB Dual Language
A Python CLI that turns an EPUB into a dual-language EPUB using OpenRouter.
The default output is paragraph aligned:
Original paragraph
Translated paragraph
This tends to work well for novels and essays because both languages stay close together without interrupting every sentence.
Features
- Translates EPUB books with OpenRouter chat models.
- Preserves the original EPUB container and stylesheet links.
- Produces a separate dual-language edition with updated title and EPUB identifiers.
- Supports paragraph or sentence interleaving.
- Supports chapter-based or token-budget chunking.
- Can translate only the beginning of a book for quick tests.
- Can add rare bracketed translator notes for important cultural or archaic references.
Example of a generated dual-language EPUB (left) vs. original (right) opened in Calibre:
Installation
Install from PyPI:
pip install epub-dual-language
Python 3.10 or newer is required.
For development, clone the repository and install it into a Python environment:
pip install -e ".[dev]"
OpenRouter API Key
The CLI reads OPENROUTER_API_KEY from the environment. You can pass it directly when
you run the command.
macOS/Linux:
OPENROUTER_API_KEY=your_openrouter_key epub-dual-language "path/to/book.epub" "English"
PowerShell:
$env:OPENROUTER_API_KEY="your_openrouter_key"; epub-dual-language "path/to/book.epub" "English"
Windows cmd:
set OPENROUTER_API_KEY=your_openrouter_key && epub-dual-language "path/to/book.epub" "English"
You can also create a .env file in the directory where you run the command:
OPENROUTER_API_KEY=your_openrouter_key
When you call epub-dual-language, it loads .env from the current working directory.
An environment variable already set in your shell takes priority over the .env value.
The repository's own .env is ignored by git.
Basic Usage
epub-dual-language "path/to/book.epub" "English"
Write to a specific output path:
epub-dual-language "path/to/book.epub" "English" --output "output/book.dual.epub"
Translate only a small beginning sample:
epub-dual-language "path/to/book.epub" "English" --limit-units 30 --output "output/sample.dual.epub"
Run without calling OpenRouter:
epub-dual-language "path/to/book.epub" "English" --dry-run --limit-units 20
Options
epub-dual-language INPUT_EPUB TARGET_LANGUAGE \
--source-language German \
--model google/gemini-3-flash-preview \
--chunking tokens \
--max-tokens 6000 \
--layout paragraph \
--translation-style italic-muted \
--translator-notes \
--limit-units 40 \
--output output/sample.dual.epub
Metadata
The generated EPUB is marked as a separate dual-language edition.
For example, an input title like:
Original Title
becomes:
Original Title (Dual English)
The OPF unique identifier is also replaced with a new deterministic dual-edition UUID. This reduces the chance that Calibre or an e-reader treats the translated edition as the same book as the source.
Chunking
--chunking tokens is the default. It walks the EPUB in reading order and packs paragraphs or sentences into model requests until the estimated --max-tokens budget is reached. Small samples can therefore fit into a single API call even if they cross EPUB file boundaries.
--chunking chapters sends one EPUB spine document at a time. Many EPUBs store chapters as separate XHTML files, so this often approximates chapter-by-chapter translation. It is simple, but very long chapters can create large requests.
Token counts are estimates based on text-like tokens, not provider billing tokens.
Layouts
--layout paragraph is the default and recommended mode for reading:
Original paragraph
Translated paragraph
--layout sentence alternates original and translated sentences inside each paragraph. It can be useful for language study, but it is less pleasant for uninterrupted reading.
Translation Styling
Translated text is italic by default so it is easy to distinguish from the original without changing the book too aggressively.
epub-dual-language "path/to/book.epub" "English" --translation-style italic
Available styles:
italicmuteditalic-mutedplain
Translator Notes
Use --translator-notes to let the model add rare, short notes in square brackets for genuinely important archaic, historical, or culturally specific references:
epub-dual-language "path/to/book.epub" "English" --translator-notes
The prompt explicitly asks the model not to explain ordinary lines or add notes everywhere.
Development
Run tests:
pytest
Run the CLI module directly:
python -m epub_dual_language.cli "path/to/book.epub" "English" --dry-run --limit-units 10
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file epub_dual_language-0.1.2.tar.gz.
File metadata
- Download URL: epub_dual_language-0.1.2.tar.gz
- Upload date:
- Size: 13.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c2b3b214fbcd5a2a5a62e8655dde6d746440276c799cfe92b88b09bb029b117
|
|
| MD5 |
4e541dfa9858e2d647b5aef999f06f67
|
|
| BLAKE2b-256 |
8974e50295157c768aeca7b383033d6078e7351e2c0b88e33cbc5f16d523bd82
|
Provenance
The following attestation bundles were made for epub_dual_language-0.1.2.tar.gz:
Publisher:
release.yml on OscarPellicer/epub-dual-language
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
epub_dual_language-0.1.2.tar.gz -
Subject digest:
2c2b3b214fbcd5a2a5a62e8655dde6d746440276c799cfe92b88b09bb029b117 - Sigstore transparency entry: 1789188927
- Sigstore integration time:
-
Permalink:
OscarPellicer/epub-dual-language@087d3f62b8f99fb590be5227f174ec030a4c6836 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/OscarPellicer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@087d3f62b8f99fb590be5227f174ec030a4c6836 -
Trigger Event:
push
-
Statement type:
File details
Details for the file epub_dual_language-0.1.2-py3-none-any.whl.
File metadata
- Download URL: epub_dual_language-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0537cd89aaf1ccccde7a1ebc00deb454cc63cd31590ae61d092cec4b54657db
|
|
| MD5 |
98db4d1abfe43603cd6821fe88453b79
|
|
| BLAKE2b-256 |
cb98ed3dbb076c59548017cbafebba9cf6bc16ac0c7b797e90849a8fa413a2f8
|
Provenance
The following attestation bundles were made for epub_dual_language-0.1.2-py3-none-any.whl:
Publisher:
release.yml on OscarPellicer/epub-dual-language
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
epub_dual_language-0.1.2-py3-none-any.whl -
Subject digest:
d0537cd89aaf1ccccde7a1ebc00deb454cc63cd31590ae61d092cec4b54657db - Sigstore transparency entry: 1789189026
- Sigstore integration time:
-
Permalink:
OscarPellicer/epub-dual-language@087d3f62b8f99fb590be5227f174ec030a4c6836 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/OscarPellicer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@087d3f62b8f99fb590be5227f174ec030a4c6836 -
Trigger Event:
push
-
Statement type: