OPEN SOURCE
software/src/output/originals.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Build lazily loaded original-language books using the reviewed word mapping."""2from __future__ import annotations34import csv5import hashlib6import json7import re8from collections import Counter9from pathlib import Path1011from src.data.versification import DATA, build_originals1213ROOT = DATA.parents[2]14WEB = ROOT / 'web/japanbible-net/web'151617def normalize_book(name):18for roman, number in [('III ', '3'), ('II ', '2'), ('I ', '1')]:19if name.startswith(roman):20name = number + name[len(roman):]21break22return name.lower().replace(' ', '').replace('revelationofjohn', 'revelation').replace('songofsolomon', 'song_of_solomon')232425def load_site(web_dir=WEB):26names = dict(re.findall(r"'([^']+)': \{ code: '([^']+)'", (web_dir / 'src/lib/data.ts').read_text()))27refs = {}28with (web_dir / 'public/JapKougo.csv').open(encoding='utf-8', newline='') as f:29for row in csv.DictReader(f):30key = (normalize_book(row['Book']), int(row['Chapter']), int(row['Verse']))31if key in refs or row['Book'] not in names:32raise ValueError(f'Invalid site reference: {key}')33refs[key] = row['Text']34codes = {normalize_book(name): code for name, code in names.items()}35if len(codes) != 66 or len({k[:2] for k in refs}) != 1189:36raise ValueError('Expected a complete 66-book site corpus')37return refs, codes383940def group_segments(words):41"""Language runs, keeping the words that make them up.4243`text` stays exactly what it was — the joined run the reader sees — so the44rendered scripture is unchanged. `words` is additive, and is what a tapped45word needs in order to be looked up at all.46"""47result = []48keys = []49for word in words:50if ' ' in word['text']:51raise ValueError(f"Word contains a space, so text cannot be split back: {word}")52if result and (result[-1]['language'], result[-1]['source']) == (word['language'], word['source']):53result[-1]['text'] += ' ' + word['text']54keys[-1].append(word.get('strong') or '')55else:56result.append({k: word[k] for k in ('language', 'source', 'text')})57keys.append([word.get('strong') or ''])58# `text` is space-joined and no word contains a space, so the words are59# recoverable by splitting it. Only the keys need storing, which costs a60# few bytes a word instead of repeating the whole text a second time.61for segment, strongs in zip(result, keys):62if any(strongs):63segment['strongs'] = ' '.join(strongs)64return result656667def build(out_dir, data_dir=DATA, web_dir=WEB):68out_dir = Path(out_dir).resolve()69# This exporter must never overwrite its inputs or the translation output.70if out_dir.is_relative_to(data_dir.resolve()):71raise ValueError('Export destination must be outside translator data')72refs, codes = load_site(web_dir)73originals, source_map = build_originals(data_dir)74unexpected = [k for k in originals if k[2] and k not in refs]75if unexpected:76raise ValueError(f'Original references outside site: {unexpected}')77books = {book: {'schema': 1, 'book': code, 'chapters': {}} for book, code in codes.items()}78missing = []79for book, chapter, verse in sorted(refs):80ch = books[book]['chapters'].setdefault(str(chapter), {'heading': [], 'verses': {}})81words = originals.get((book, chapter, verse), [])82ch['verses'][str(verse)] = group_segments(words)83if not words:84missing.append(f'{book}:{chapter}:{verse}')85for (book, chapter, verse), words in originals.items():86if verse == 0:87books[book]['chapters'][str(chapter)]['heading'] = group_segments(words)88files = {}89out_dir.mkdir(parents=True, exist_ok=True)90for book, payload in books.items():91text = json.dumps(payload, ensure_ascii=False, separators=(',', ':')) + '\n'92filename = codes[book] + '.json'93(out_dir / filename).write_text(text, encoding='utf-8')94files[filename] = hashlib.sha256(text.encode()).hexdigest()95summary = {'schema': 1, 'source_references': len(source_map),96'site_references': len(refs), 'mapped_source_references': sum(any(t != k for t, _, _ in spans) for k, spans in source_map.items()),97'heading_chapters': sum(k[2] == 0 for k in originals),98'words_by_language': dict(Counter(w['language'] for ws in originals.values() for w in ws)),99'without_original': missing, 'files': files,100'source_files': {str(p.relative_to(data_dir)): hashlib.sha256(p.read_bytes()).hexdigest() for language in ('hebrew', 'greek') for p in sorted((data_dir/'sources'/language).glob('*.json'))},101'map_sha256': hashlib.sha256((data_dir/'versification/oshb-VerseMap.xml').read_bytes()).hexdigest()}102(out_dir/'manifest.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2)+'\n')103return summary104