Add Books — read EPUB-imported books in Practice with tap-to-define
New "Books" row in the Practice tab opens a library of bundled bilingual books. Each chapter renders Spanish paragraph-by-paragraph; tap any word for a definition sheet (DictionaryService with on-device AI fallback), or toggle the toolbar button to swap to the pre-computed English translation inline. Local-only Book + BookChapter SwiftData models added to the local container schema (reset version bumped to 5). DataLoader.seedBooks walks the bundle for `book_*.json` resources, so future books drop in without touching app code — just bundle a new JSON and bump bookDataVersion. First book: Olly Richards' "Spanish Short Stories For Beginners Vol 2" — 13 chapters, 2,646 paragraphs, bilingual. Scripts/books/ is the repeatable pipeline for future EPUBs: extract_epub.py → translate_chapters.py (per-chapter resumable jobs) → bundle_book.py. Translation is done by parallel Claude Code subagents reading per-job input files and writing output files — no API key required, matching the pattern used for the textbook vocab vision pass. See Scripts/books/README.md for the full how-to. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Orchestrate the books pipeline: EPUB -> chapters.json -> per-chapter job
|
||||
# manifest -> (translation by Claude Code subagents) -> bundled book_<slug>.json.
|
||||
#
|
||||
# This script DOES NOT run the LLM translation pass. After Phase 2 it stops
|
||||
# and prints how many jobs are pending. Use Claude Code subagents (or a fresh
|
||||
# session per the README) to fill in build/<slug>/jobs/*.output.json, then
|
||||
# re-run this script — it will pick up where it left off via Phase 3.
|
||||
#
|
||||
# Usage:
|
||||
# ./run.sh <epub_path> [--slug SLUG] [--batch-size N]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$HERE"
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "usage: $0 <epub_path> [--slug SLUG] [--batch-size N]"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
EPUB="$1"; shift
|
||||
SLUG=""
|
||||
BATCH_SIZE="30"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--slug) SLUG="$2"; shift 2 ;;
|
||||
--batch-size) BATCH_SIZE="$2"; shift 2 ;;
|
||||
*) echo "unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
EPUB_ABS="$(cd "$(dirname "$EPUB")" && pwd)/$(basename "$EPUB")"
|
||||
|
||||
echo "=== Phase 1: extract_epub.py ==="
|
||||
if [[ -n "$SLUG" ]]; then
|
||||
python3 extract_epub.py "$EPUB_ABS" --slug "$SLUG"
|
||||
else
|
||||
python3 extract_epub.py "$EPUB_ABS"
|
||||
fi
|
||||
|
||||
# If --slug wasn't passed, recover the slug from the chapters file just written.
|
||||
if [[ -z "$SLUG" ]]; then
|
||||
SLUG=$(python3 -c "import json,glob; p=sorted(glob.glob('build/*/chapters.json'), key=lambda x: -__import__('os').path.getmtime(x))[0]; print(json.load(open(p))['slug'])")
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Phase 2: translate_chapters.py ==="
|
||||
python3 translate_chapters.py "$SLUG" --batch-size "$BATCH_SIZE"
|
||||
|
||||
PENDING_FILE="build/$SLUG/jobs/_pending.txt"
|
||||
PENDING_COUNT=$(wc -l < "$PENDING_FILE" | tr -d ' ')
|
||||
|
||||
echo
|
||||
echo "=== Phase 3: bundle_book.py ==="
|
||||
if [[ "$PENDING_COUNT" -gt 0 ]]; then
|
||||
echo " $PENDING_COUNT translation job(s) still pending."
|
||||
echo " Run the Claude Code subagent translation step (see README.md), then re-run this script."
|
||||
echo " Bundling with empty placeholders so you can preview app structure now."
|
||||
python3 bundle_book.py "$SLUG"
|
||||
else
|
||||
python3 bundle_book.py "$SLUG" --require-all
|
||||
fi
|
||||
Reference in New Issue
Block a user