This skill uses a streaming pipeline. It does not pre-split the source file into many raw part files on disk.
The workflow is:
raw.md.raw.md with RawReader.py, using a byte offset.out/part_XXX.md._vartemp.json.TenRieng.md (proper names) and XungHo.md (forms of address) immediately when new entries appear.chapters index in _vartemp.json when chapter numbering or chapter titles are known.merge_parts.py, or merge chapter files with merge_chapters.py.text
<project-dir>/
├── raw.md # Complete source text
├── _vartemp.json # Translation checkpoint + part/chapter indexes
├── TenRieng.md # Proper-name glossary
├── XungHo.md # Forms-of-address matrix
├── out/ # part_001.md, part_002.md, ...
├── Chapters/ # Optional: one translated file per chapter
├── Full.md / full.md # Merged output
├── Full.epub # EPUB output
└── assets/ # Images, if any
_vartemp.json state#The checkpoint file is plain UTF-8 JSON. It replaces the old _vartemp.bin filename.
A typical state is:
json
{
"version": "1.2.0",
"completed": [
{"part": 1, "start": 0, "end": 12288},
{"part": 2, "start": 12288, "end": 24100}
],
"chapters": [
{
"index": 1,
"rawIdx": 1,
"transIdx": 1,
"number": 1,
"title": "Optional chapter title"
}
],
"next_offset": 24100
}
completed is the ordered index of translated parts.part number and source byte range [start, end).next_offset is the byte offset from which the next source chunk must be read.chapters is an ordered chapter index.rawIdx is the chapter's ordinal position in the source/raw text.transIdx is the chapter's ordinal position in the translated output.rawIdx and transIdx may differ when chapters are omitted, merged, or otherwise remapped.number is the displayed/source chapter number.title is optional metadata used for the translated chapter heading and EPUB TOC.title as null._vartemp.json must remain valid JSON after every progress update.ScriptPurposescripts/convert_to_md.pyConvert .txt/.epub/.docx/.html/.pdf to Markdownscripts/RawReader.pyRead an approximately 12 KiB chunk from raw.md and cut only at \n``scripts/progress.pyManage _vartemp.json, completed-part indexes, and optional chapter indexesscripts/merge_parts.pyConcatenate out/part_*.md into a full Markdown filescripts/format_output.pyNormalize output formatting and chapter headingsscripts/verify_part.pyPerform a quick translation-part integrity checkscripts/merge_chapters.pyMerge Chapters/ files by chapter numberscripts/md2epub.pyConvert merged Markdown to EPUB with TOC, CSS, and local images
bash
python scripts/convert_to_md.py <source_file>
# Rename or copy the resulting Markdown file to raw.md if necessary.
mkdir -p out
TenRieng.md#markdown
# Proper Names
| Source Name | Vietnamese Name | Notes |
|-------------|-----------------|-------|
XungHo.md#markdown
# Forms of Address
Matrix: [Speaker] × [Listener], cell = `Self-reference\Address`
Keep these files authoritative throughout the entire translation. When a new proper name or relationship appears, update the relevant table before translating the next part.
bash
python scripts/progress.py get
python scripts/progress.py next-part
python scripts/progress.py listt
bash
OFFSET=$(python scripts/progress.py get)
PART=$(python scripts/progress.py next-part)
python scripts/RawReader.py raw.md $OFFSET 12288
RawReader.py writes:
translated-source text to stdout
metadata to stderr:
SKIPREADNEXT_OFFSETEOFTOTALAlways use the returned NEXT_OFFSET as the end offset recorded for the completed part.
out/part_XXX.md#For the current chunk:
out/part_XXX.md.XXX is a zero-padded 3-digit part number.TenRieng.md and XungHo.md immediately when new entries appear.\u3000 as indentation._vartemp.json.After the translated part has been written and checked:
bash
python scripts/progress.py add <START_OFFSET> <NEXT_OFFSET>
For example:
bash
python scripts/progress.py add 12288 24100
To add a chapter index:
bash
python scripts/progress.py chapter-add 3
python scripts/progress.py chapter-name 3 "The Hidden Truth"
# When raw and translated chapter positions differ:
python scripts/progress.py chapter-map 5 4 5 "The Hidden Truth"
List chapter indexes:
bash
python scripts/progress.py chapter-list
To retranslate a part:
bash
python scripts/progress.py remove-part 5
Then read the required source range again and overwrite out/part_005.md.
Repeat the one-part cycle until EOF=1.
For ordinary streaming output:
bash
python scripts/merge_parts.py out/ full.md
If the project uses one file per chapter:
bash
python scripts/merge_chapters.py --title "Book Title"
Keep rawIdx and transIdx stable when chapters are remapped. transIdx
is the canonical translated order used for EPUB chapter filenames and TOC
entries.
bash
python scripts/format_output.py out/ titles.json
titles.json may map chapter numbers to requested titles, for example:
json
{
"1": "The Secret of the Parents",
"7": "The Hidden Drive",
"ngoai_truyen": "Extra Chapter"
}
The EPUB script uses ebooklib and markdown. It reads _vartemp.json
to preserve transIdx and requested chapter titles. Each translated chapter
is emitted as a separate chapter_XXX.html content document, where XXX
is the zero-padded transIdx. The EPUB NCX/Nav TOC points directly to these
chapter documents, so EReaders receive one stable TOC entry per chapter.
If the environment already uses uv, the dependencies can be supplied for
this command without modifying the global Python installation:
bash
uv run --with ebooklib --with markdown python scripts/md2epub.py \
--input Full.md \
--output Full.epub \
--title "Book Title" \
--cover assets/cover.png \
--state _vartemp.json
Do not install additional packages unless the user explicitly requests an environment change and the package source and requirement have been verified.
markdown
# Proper Names
| Source Name | Vietnamese Name | Notes |
|-------------|-----------------|-------|
| Alice | Alice | Female protagonist |
markdown
# Forms of Address
| Speaker \ Listener | An | Stepmother |
|--------------------|----|------------|
| An | - | Con / Dì |
| Stepmother | Mẹ / Con | - |
PlaceholderMeaning* *Narrative text: involuntary physical reactions, sensory details, and atmospheric descriptions." "Audible dialogue spoken by {char} in the surrounding environment.` `Concealed communication: internal thought, telepathy, encrypted phone/SMS, spiritual transmission, or another secret channel that is not audible to nearby characters. Use sparingly.
Standard output structure:
text
# Chương N: [chapter title]
*[Physical reaction and sensory anchor first: what the body feels, smells, or hears.]*
"[Optional audible dialogue, only when the character would actually speak aloud.]"
`[Optional concealed communication — internal thought or secret channel.]`
*[Continuation of the action or a shift in attention.]*
Do not write a complete translation draft in THINKING and then copy that same draft into an execution/code step. This duplicates the content in the model context and wastes tokens.
Translate directly into the final data structure used to write the output file.
The source already defines paragraph boundaries. Preserve them.
After writing out/part_XXX.md, compare the number of non-empty paragraphs in the source chunk and translated output.
0 is ideal.1 is acceptable because a translation may legitimately combine or split one short paragraph.1 is a warning sign for truncation, omission, or accidental over-translation.Do not print or reread the last three lines merely to confirm that the file was written.
Do not spend several thousand tokens rereading an old translated part just to imitate its prose style.
Use the authoritative resources instead:
references/thanh-ngu-va-tu-ngu.mdTenRieng.mdXungHo.mdTranslate naturally, but preserve established Sino-Vietnamese forms for Chinese idioms and classical expressions when the reference requires them.
If a new recurring idiom or terminology is needed, add it to the appropriate reference file rather than bloating SKILL.md.
When writing translated output:
\u3000 for indentation.# Chương N: [title] for chapters.# Giới thiệu for introductions.# Ngoại truyện: [title] when an interlude title is known.RawReader.py is responsible for cutting the chunk at \n.
Never manually cut the raw chunk in the middle of a sentence or paragraph.
One part per translation turn is mandatory.
Do not batch two or more parts together. The intended cycle is:
text
read one part
→ translate it
→ write out/part_XXX.md
→ verify it
→ update _vartemp.json
→ continue
Do not translate parts with parallel subagents.
Parallel translation has previously caused:
The main agent should translate the parts sequentially.
If a background process creates a zero-byte Markdown output:
bash
find out -name '*.md' -size 0 -delete
Remove the broken file immediately and translate that part again.
Before merging, make sure no zero-byte part remains.
Preserve these checkpoint files throughout every translation task:
out/_vartemp.jsonTenRieng.mdXungHo.mdAt the beginning of each translation session, inspect:
bash
python scripts/progress.py listt
ls out/
Do not restart from part 1 if the checkpoint is valid.
The token dashboard may report approximately 100–160× the useful raw/output token count because the agent loop can resend the working context on each tool call.
See:
text
references/token-usage-diagnostics.md
for diagnostics and interpretation.
When continuing a translation in a new session, write the output in final format immediately.
Do not intentionally write an unformatted intermediate version and plan to fix it later.
Add a new proper name to TenRieng.md the first time it appears.
Do not change a previously established translation halfway through the novel unless the user explicitly requests a terminology change.
Follow:
text
references/thanh-ngu-va-tu-ngu.md
For example, if the reference specifies a fixed Vietnamese term, use that exact term consistently.
Treat XungHo.md as a logical matrix.
When a new character or relationship appears:
Markdown files may contain a UTF-8 BOM. The scripts should read such files safely.
Do not use a shell heredoc for large Vietnamese or Chinese content. It can easily cause quoting or encoding problems.
Prefer Python Path.write_text().
For content containing many double quotes, a JSON temporary file is safer:
python
import json
from pathlib import Path
paras = ['Paragraph 1 ...', 'Paragraph 2 ...']
tmp = Path("_tmp_paras.json")
tmp.write_text(
json.dumps(paras, ensure_ascii=False),
encoding="utf-8",
)
paras = json.loads(tmp.read_text(encoding="utf-8"))
Path("out/part_001.md").write_text(
"\n\n".join(paras) + "\n",
encoding="utf-8",
)
tmp.unlink()
Delete temporary files immediately after the output has been written.
Do not leave _*.json temporary files or prompt artifacts in _prompts/.
workdir containing Vietnamese characters when possible.Path object rather than reconstructing the name manually.Path objects.references/thanh-ngu-va-tu-ngu.md — idioms and sensitive/terminology referencereferences/token-usage-diagnostics.md — token-usage diagnosticsreferences/session-worked-example.md — worked translation-session example