A bilingual-first fork of CrossPoint Reader. Read in two languages without turning an e-reader into a phone.
CrossLingua keeps the capable, open-source CrossPoint reading experience and builds a complete bilingual workflow around it: translate an EPUB directly on the device or prepare it with the Ebook Translator Calibre plugin, then choose how much of each language you want to see.
Runs on: Xteink X3, X4, X4 Pro, M5Stack Paper Mono, and Seeed Studio XIAO ePaper Display Board (Sticky). X4 Pro, Paper Mono, and Sticky use dedicated builds with their device-specific display, touch, key, power, and frontlight integrations.
![]() Interlinear Translation follows the source line by line. |
![]() Side by Side Two languages, aligned in parallel columns. |
![]() Bookshelf Browse books visually by their covers. |
Lingua can translate the current chapter or an entire EPUB over Wi-Fi. The result is cached on the SD card, so after translation the book can be read offline and its display mode can be changed without sending the text again. The original EPUB is never modified.
CrossLingua also recognizes translations embedded in an EPUB as language-tagged paragraphs. That makes the same reading modes available for bilingual books prepared outside the device, including books produced with the Ebook Translator Calibre plugin.
- On the device: choose the source and target languages, translate one chapter or the whole book, and keep the generated bilingual copy in the book's SD-card cache. A single chapter usually takes 1–3 minutes. A full book usually takes 10–30 minutes. Actual time depends on your Wi-Fi connection, translation-engine availability, the amount of text, and similar conditions.
- With Calibre: prepare a bilingual EPUB with the Ebook Translator plugin and transfer it to the reader. CrossLingua detects its language-tagged translation paragraphs and applies the same Lingua modes without translating the book again.
| Engine | API key | Notes |
|---|---|---|
| Google v2 | No | Default free Google backend |
| Google v1 / HTML | No | Free Google backend with HTML-aware requests |
| Google legacy | No | Compatibility option for older settings; currently routed through Google v2 |
| Azure | No | Keyless Microsoft Edge translation endpoint |
| DeepL | Yes | DeepL API Free |
| DeepL Pro | Yes | DeepL paid API |
| OpenAI | Yes | Chat Completions translation |
| DeepSeek | Yes | DeepSeek chat translation |
| Gemini | Yes | Google Gemini translation |
Free, undocumented translation endpoints may be rate-limited, changed, or withdrawn by their providers. Text is sent to the selected provider only while a translation is being created.
The same translated book can be viewed in any of these modes. Mode-specific options appear directly below the active mode in the Lingua menu.
Compact translation annotations sit above the source lines they belong to. This keeps both languages in one reading flow without duplicating full paragraph blocks. The annotation colour can be set to Black, Grey, or Light Grey.
The source and translation are laid out as synchronized left and right columns. Only the translation column's colour is configurable: Black, Grey, or Light Grey. It works in both landscape and portrait orientations.
|
|
Original and translated paragraphs alternate in the normal page flow. Translations can use Dimmed or Dimmed Light ink and either the Same or a Smaller text size.
Read the uncluttered original page and reveal one sentence translation at a time in a small overlay. Choose the front or side button pair for sentence stepping, choose whether the control loops on the page or turns the page and continues, and set translation text to the Same or a Smaller size.
![]() Tooltip in landscape orientation. |
![]() Tooltip in portrait orientation. |
Keep the original page visible and open a larger translation overlay for the text on that page. Choose the front or side buttons for the overlay controls and use the Same or a Smaller translation size.
Show only the source-language text. This is useful for immersion reading while keeping translations available for a quick switch to Tooltip or Page Translation.
Show only the translated text as a clean monolingual book.
Render the source and translation together in the EPUB's normal flow, using the same text colour. This is the neutral compatibility mode for already bilingual EPUBs.
Bookshelf is a visual alternative to the file browser. It renders a 3×3 grid, generates and caches EPUB cover thumbnails in the background, preserves folder navigation, and lets you open a book directly from its cover.
![]() Organize the library into series and folders. |
![]() Browse a complete series by its covers. |
![]() Cover previews are generated and cached in the background. |
We developed EdsLab, a custom reading font created specifically for
CrossLingua and comfortable bilingual reading on an e-ink screen. It is the
default font, but you are not locked into it: custom .cpfont families can
still be installed from the SD card. See
Custom SD-card fonts.
CrossLingua remains a full CrossPoint Reader fork, not a translation-only
experiment. It includes EPUB 2/3 rendering, .xtc/.xtch, .txt and .bmp
support, images, embedded styles, hyphenation, kerning, footnotes, bookmarks,
StarDict dictionaries, chapter navigation, go-to-percent, focus reading,
auto page turn, orientation control, screenshots, custom fonts, themes,
button remapping, KOReader progress sync, OPDS, WebDAV, Calibre wireless
transfer, a web file manager and settings UI, EPUB optimization, OTA updates,
24 UI languages and RTL support.
CrossLingua is actively evolving. This section will track the next Lingua modes, translation workflows, library improvements, and device support as they are planned.
- Roadmap details coming soon
Some Xteink units purchased from third-party stores (e.g. AliExpress) ship with USB flashing locked from the factory. If your device is locked, use the Xteink Unlocker at https://crosspointreader.com/#unlock-tool before flashing.
You do not need this tool if you bought your device directly from xteink.com. Those units are not locked.
Not sure if your device is locked? Power it on, connect the USB-C cable, and try flashing via the web flasher first (see Install firmware below). If the browser's serial device picker does not show your device, try a different USB port or browser before assuming the device is locked. Only reach for the unlocker if the device still doesn't appear.
Each hardware family has its own release binary. Download the file that exactly matches your device from the CrossLingua releases page:
| Device | Release file | Chip |
|---|---|---|
| Xteink X3 / X4 | firmware.bin |
ESP32-C3 |
| Xteink X4 Pro | firmware-x4pro.bin |
ESP32-S3 |
| M5Stack Paper Mono | firmware-papermono.bin |
ESP32-S3 |
| Seeed Studio XIAO ePaper Display Board (Sticky) | firmware-sticky.bin |
ESP32-S3 |
Warning
Never flash a binary intended for another device. CrossLingua validates the chip and board identity during in-device updates, but external flashing tools may not provide the same protection.
Connect the device to Wi-Fi, then open Settings → System → Check for
updates. The built-in OTA updater checks the latest CrossLingua release and
automatically selects the correct file for the detected hardware, so you do
not need to download or choose a .bin manually. Confirm the update when
prompted, keep the device powered while it is installing, and wait for it to
restart.
To update from an SD card instead:
- Download the matching file from the table above.
- Copy it anywhere on the SD card.
- Open the firmware update option on the device and select the downloaded
.binfile. - Confirm the update and keep the device powered until it restarts.
The SD-card picker accepts any .bin filename, so files such as
firmware-x4pro.bin do not need to be renamed to firmware.bin.
The web-flasher procedure below is for Xteink X3 and X4 only. Do not select an X3/X4 profile for X4 Pro, Paper Mono, or Sticky.
- Connect your device to your computer via USB-C and wake/unlock the device
- Download
firmware.binfrom the CrossLingua releases page. - Open https://crosspointreader.com/#flash-tools, select X3 or X4, choose Custom .bin, and upload the downloaded firmware.
To install a specific version, download firmware.bin from that version's
release instead of the latest release and follow the same steps.
Some Xteink devices purchased from third-party stores must be unlocked first; see USB-locked devices.
The X3/X4 web-flasher and ESP32-C3 command below are not compatible with these ESP32-S3 devices. If CrossPoint or CrossLingua is already installed, use its SD-card firmware updater with the matching release file. For development or a first installation from source, use the corresponding PlatformIO environment:
pio run -e x4pro -t upload # Xteink X4 Pro
pio run -e papermono -t upload # M5Stack Paper Mono
pio run -e sticky -t upload # Seeed StickyConnect only the target device while uploading. Board-specific USB boot mode, drivers, and connection steps depend on the hardware revision; follow the device manufacturer's instructions if PlatformIO cannot detect it.
To revert to the official firmware, you can also flash the latest official firmware using https://crosspointreader.com/#flash-tools.
This command writes the shared X3/X4 ESP32-C3 application image. Do not use it for X4 Pro, Paper Mono, or Sticky.
- Install
esptool:
pip install esptool- Download the X3/X4
firmware.binfrom the CrossLingua releases page. - Connect your device via USB-C.
- Find the device port. On Linux, run
dmesgafter connecting. On macOS:
log stream --predicate 'subsystem == "com.apple.iokit"' --info- Flash:
esptool.py --chip esp32c3 --port /dev/ttyACM0 --baud 921600 write_flash 0x10000 /path/to/firmware.binAdjust /dev/ttyACM0 to match your system.
See Development quick start below.
Convert your own TTF/OTF files into .cpfont files that load from the SD card. No firmware reflash is needed.
- Go to https://crosspointreader.com/fonts and open the "SD-card font builder" form.
- Upload up to four styles (regular, bold, italic, bold-italic), set the family name, point sizes, and Unicode range.
- Download the generated
.cpfontfiles. - Copy them to your SD card under
/fonts/YourFont/(or/.fonts/YourFont/to hide the folder). - Select the font on the device from the font settings.
Conversion runs the firmware repo's lib/EpdFont/scripts/fontconvert_sdcard.py script unmodified, so output matches a local host build.
- User Guide
- Web server usage
- Web server endpoints
- Project scope
- Contributing docs
- Touch and UI development - how to build new screens on the FreeInkUI activity bases (UiListActivity and friends), plus build envs for the non-Xteink touch devices
- pioarduino or VS Code + pioarduino plugin
- Python 3.8+
clang-format21- USB-C cable supporting data transfer
git clone --recursive https://github.com/ed-fruty/crosslingua-reader
cd crosslingua-reader
# if cloned without --recursive:
git submodule update --init --recursiveNix/NixOS users can enter the development shell with either nix develop (flakes) or nix-shell:
nix develop -f nix
# or
nix-shell nixTo flash a connected ESP32-C3 device, enable PlatformIO's udev rules in your NixOS configuration:
services.udev.packages = with pkgs; [ platformio-core.udev ];After rebuilding the system configuration, reconnect the device or reload udev rules.
pio run --target upload./bin/clang-format-fix
pio check -e default
pio run -e defaultAfter flashing the new features, it’s recommended to capture detailed logs from the serial port.
First, make sure all required Python packages are installed:
python3 -m pip install pyserial colorama matplotlibAfter that run the script:
# For Linux
# This was tested on Debian and should work on most Linux systems.
python3 scripts/debugging_monitor.py
# For macOS
python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101Minor adjustments may be required for Windows.
CrossLingua is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based on this constraint.
The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the
cache. This cache directory exists at .crosspoint on the SD card. The structure is as follows:
.crosspoint/
├── epub_<hash>/ # one directory per book, named by content hash
│ ├── progress.bin # reading position (chapter, page, etc.)
│ ├── cover.bmp # generated cover image
│ ├── book.bin # metadata: title, author, spine, TOC
│ ├── css_rules.cache # parsed CSS rule cache
│ ├── img_* # rendered image cache files
│ └── sections/ # per-chapter layout cache
│ ├── 0.bin
│ ├── 1.bin
│ └── ...
├── settings.json # device settings
├── state.json # resume/runtime state
└── recent.json # recent books list
Removing /.crosspoint clears all cached metadata and forces a full regeneration on next open. Book deletes, overwrites, and moves done through the firmware or web UI clear or re-key matching caches; manual SD-card edits may leave stale cache directories behind.
For more details on the internal file structures, see the file formats document.
If CrossLingua is useful to you and you would like to support further development, testing, and maintenance, you can leave a donation here:
Donations are optional. Bug reports, testing results, and suggestions are valuable contributions too.
Contributions are welcome. If you're new to the codebase, start with the contributing docs. For upstream CrossPoint work, see its ideas discussion board.
Everyone here is a volunteer, so please be respectful and patient. For governance and community expectations, see GOVERNANCE.md.
CrossLingua Reader is not affiliated with Xteink or any device manufacturer.
Huge shoutout to diy-esp32-epub-reader, which inspired this project.







