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:
Trey T
2026-05-11 09:21:44 -05:00
parent ade091f108
commit 09e49bda2c
17 changed files with 6782 additions and 1 deletions
@@ -0,0 +1,32 @@
import Foundation
import SwiftData
/// A long-form bilingual book bundled with the app. Chapter content lives in
/// `BookChapter` rows; this model carries the per-book metadata.
@Model
public final class Book {
@Attribute(.unique) public var id: String = "" // matches `slug`
public var slug: String = ""
public var title: String = ""
public var author: String = ""
public var language: String = ""
public var chapterCount: Int = 0
public var accentColorHex: String = ""
public init(
slug: String,
title: String,
author: String,
language: String,
chapterCount: Int,
accentColorHex: String
) {
self.id = slug
self.slug = slug
self.title = title
self.author = author
self.language = language
self.chapterCount = chapterCount
self.accentColorHex = accentColorHex
}
}