Pre-publication review draft
日常訳NICHIJOYAKU日本語 ↗

SOURCE EXPLORER / EVIDENCE BROWSER

prompt.py

Inspect the implementation and evidence, line by line.

Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.

prompt.py536 linesDownload raw ↓
1"""Prompt construction for Bible translation."""23from __future__ import annotations45from typing import TYPE_CHECKING67import os89from src.context.glossary import GlossaryManager10from src.models.pericope import ChunkTranslationContext11from src.models.style import TranslationStyle, build_system_prompt12from src.models.verse import TranslationContext, Verse, VerseReference13from src.pipeline.morphology import select_morphology_notes1415# Morphology exposure measured as quality-neutral in two A/Bs (famous and16# hard-passage sets) — default off to save prompt tokens; enable to experiment.17MORPHOLOGY_ENABLED = os.environ.get("TRANSLATE_BIBLE_MORPH", "0") == "1"181920def _morphology_notes_for(verse: Verse, limit: int = 6) -> list[str]:21    """Morphology notes for a verse, respecting the feature flag."""22    if not MORPHOLOGY_ENABLED:23        return []24    if verse.greek_words:25        return select_morphology_notes(verse.greek_words, "greek", limit=limit)26    if verse.hebrew_words:27        return select_morphology_notes(verse.hebrew_words, "hebrew", limit=limit)28    return []2930if TYPE_CHECKING:31    from src.context.translation_memory import TranslationMemoryManager32    from src.models.translation_memory import TMMatch3334# Default system prompt (for backward compatibility)35SYSTEM_PROMPT = """You are an expert Bible translator creating a modern Japanese translation.3637=== TRANSLATION PHILOSOPHY ===3839EQUIVALENCE APPROACH: Dynamic/Functional40- Prioritize thought-for-thought translation over word-for-word literalism41- Ask "how would a native Japanese speaker naturally express this idea?"42- Preserve the meaning and impact, not necessarily the exact syntax43- Restructure sentences for natural Japanese flow when needed4445GENDER LANGUAGE: Moderate Inclusive46- Use gender-inclusive language for clearly generic references47- ἀδελφοί (adelphoi): render as "兄弟姉妹" when context indicates mixed group48- ἄνθρωπος (anthropos): use "人" or "人々" for generic humanity, not just "男"49- Preserve gender-specific language where the original clearly indicates it5051READABILITY: Web-native (young adults, online-media register)52- Target readers in their 20s whose daily reading is online media53- Short sentences: one idea per sentence54- Everyday vocabulary only; prefer 和語 over stiff literary 漢語55- Keep every concrete content word — modernize the wording, never the content5657THEOLOGICAL TRADITION: Protestant Evangelical58- Follow Protestant canon and interpretive tradition59- Translate key theological terms in line with evangelical understanding60- Examples: δικαιοσύνη as "義" (righteousness), σωτηρία as "救い" (salvation)6162TEXTUAL BASIS: MorphGNT/SBLGNT63- Follow the provided source text without incorporating textual variants64- Do not emend or correct based on other manuscript traditions6566TERM CONSISTENCY: Balanced67- Maintain consistent translation for key theological terms (provided in glossary)68- Allow contextual variation for common words where meaning shifts69- Prioritize natural reading over rigid concordance for non-technical vocabulary7071=== TARGET AUDIENCE ===72The translation should be accessible to young adult Japanese nonbelievers who are not familiar with Christianity.73- Use vocabulary and expressions that require no prior church background74- Explain concepts through natural phrasing rather than relying on traditional Christian terminology75- Avoid insider language that would confuse those unfamiliar with the faith76- HOWEVER: No compromise should be made in conveying the true Biblical meaning of the text77- Accuracy to the original meaning takes absolute priority over accessibility78- When tension exists between accessibility and faithfulness, choose faithfulness7980=== TARGET STYLE ===81Modern natural Japanese (現代のウェブ記事・ナレーションのような平易な日本語)82- Use contemporary vocabulary and grammar83- Avoid archaic expressions (なり、たり、候)84- Narrative, exposition, and poetry: plain form (である体/だ体), as in 新共同訳 and 口語訳85- Polite form (です/ます) ONLY inside quoted speech, when natural for the speaker86- Keep the same register consistently across every verse of a passage87- Orthography: first-person singular is わたし (hiragana, not 私); keep orthography consistent88- Prioritize clarity and accessibility8990=== LITERARY QUALITY (by genre) ===91Match the literary character of the source genre:92- Hebrew poetry: preserve parallel line structure, concrete imagery, quiet rhythm93- Narrative: clear flowing prose; preserve deliberate repetition and refrains94- Epistles: keep logical connectives and argument flow; no fragmenting of one thought95- Sayings/beatitudes: preserve the parallel formula across the whole series9697=== VERSE COHESION ===98A verse is often part of a longer sentence. Use the preceding and following verses99in CONTEXT to make your translation read as a smooth continuation of the passage.100101=== INPUT FORMAT ===102You will receive:1031. Source text (Greek for NT, Hebrew for OT)1042. Reference translations (historical Japanese and English)1053. Surrounding context (preceding and following verses)1064. Glossary terms to use consistently107108=== OUTPUT CONTRACT (strict) ===109Output ONLY the final Japanese translation of the target verse.110- No explanations, notes, romanization, or commentary111- No alternative renderings, no self-corrections, no English of any kind112- Decide on your final wording BEFORE writing; output exactly one rendering"""113114115class PromptBuilder:116    """Builds prompts for translation."""117118    def __init__(119        self,120        glossary: GlossaryManager,121        style: TranslationStyle | None = None,122        translation_memory: TranslationMemoryManager | None = None,123    ) -> None:124        """Initialize the prompt builder.125126        Args:127            glossary: Glossary manager for term formatting.128            style: Translation style configuration. If None, uses default.129            translation_memory: Translation memory manager for TM matches.130        """131        self.glossary = glossary132        self._style = style133        self._translation_memory = translation_memory134        self._system_prompt: str | None = None135136    @property137    def style(self) -> TranslationStyle:138        """Get the current translation style."""139        if self._style is None:140            self._style = TranslationStyle()141        return self._style142143    @style.setter144    def style(self, value: TranslationStyle) -> None:145        """Set the translation style and invalidate cached prompt."""146        self._style = value147        self._system_prompt = None148149    def build_prompt(150        self,151        context: TranslationContext,152        tm_matches: list[TMMatch] | None = None,153    ) -> str:154        """Build a translation prompt from context.155156        Args:157            context: Full translation context.158            tm_matches: Optional list of translation memory matches.159160        Returns:161            Formatted prompt string for the LLM.162        """163        parts = []164165        # Source texts section166        parts.append(self._format_source_texts(context.target_verse))167168        # Selective morphology (key verb forms)169        morph_notes = _morphology_notes_for(context.target_verse)170        if morph_notes:171            parts.append(172                "\nMORPHOLOGY (parsing of key verb forms — use to disambiguate"173                " tense/aspect, voice, and participles):\n"174                + "\n".join(f"  {n}" for n in morph_notes)175            )176177        # Reference translations section178        parts.append(self._format_reference_translations(context.target_verse))179180        # Context section181        parts.append(self._format_context(context))182183        # Translation Memory section (if matches provided)184        if tm_matches:185            parts.append(self._format_tm_matches(tm_matches))186187        # Glossary section188        if context.glossary_terms:189            parts.append(self._format_glossary(context))190191        # Translation instruction192        ref = context.target_verse.reference193        parts.append(f"\nTRANSLATE: {ref.book.title()} {ref.chapter}:{ref.verse}")194        parts.append("Provide only the Japanese translation:")195196        return "\n".join(parts)197198    def _format_tm_matches(self, matches: list[TMMatch]) -> str:199        """Format translation memory matches for prompt.200201        Args:202            matches: List of TM matches.203204        Returns:205            Formatted string for prompt inclusion.206        """207        if not matches:208            return ""209210        lines = ["\nTRANSLATION MEMORY (similar past translations for reference):"]211        for match in matches[:3]:  # Limit to top 3212            similarity_pct = int(match.similarity * 100)213            seg = match.segment214            lines.append(f"  [{similarity_pct}%] {seg.reference}: {seg.target_text}")215216        return "\n".join(lines)217218    def _format_source_texts(self, verse: Verse) -> str:219        """Format source texts section."""220        lines = ["SOURCE TEXT:"]221222        if verse.greek_text:223            lines.append(f"Greek: {verse.greek_text}")224            if verse.greek_translit:225                lines.append(f"Romanization: {verse.greek_translit}")226        elif verse.hebrew_text:227            lines.append(f"Hebrew: {verse.hebrew_text}")228            if verse.hebrew_translit:229                lines.append(f"Romanization: {verse.hebrew_translit}")230231        return "\n".join(lines)232233    def _format_reference_translations(self, verse: Verse) -> str:234        """Format reference translations section."""235        lines = ["\nREFERENCE TRANSLATIONS:"]236237        if verse.japanese_meiji:238            lines.append(f"- 明治元訳 (1887): {verse.japanese_meiji}")239        if verse.japanese_taisho:240            lines.append(f"- 大正改訳 (1917): {verse.japanese_taisho}")241        if verse.japanese_kougo:242            lines.append(f"- 口語訳 (1955): {verse.japanese_kougo}")243        if verse.english_kjv:244            lines.append(f"- KJV: {verse.english_kjv}")245        if verse.english_asv:246            lines.append(f"- ASV: {verse.english_asv}")247248        return "\n".join(lines)249250    def _format_context(self, context: TranslationContext) -> str:251        """Format context section."""252        lines = ["\nCONTEXT:"]253254        # Book context255        if context.book_metadata:256            meta = context.book_metadata257            lines.append(258                f"Book: {meta.name_english} ({meta.name_japanese}) - {meta.description}"259            )260            if meta.themes:261                lines.append(f"Themes: {', '.join(meta.themes)}")262263        # Chapter summary264        if context.chapter_summary:265            lines.append(f"\nChapter Summary: {context.chapter_summary.summary}")266267        # Preceding verses268        if context.preceding_verses:269            lines.append("\nPreceding verses:")270            for v in context.preceding_verses:271                text = self._get_best_text(v)272                lines.append(f"  {v.reference.verse}: {text}")273274        # Following verses275        if context.following_verses:276            lines.append("\nFollowing verses:")277            for v in context.following_verses:278                text = self._get_best_text(v)279                lines.append(f"  {v.reference.verse}: {text}")280281        return "\n".join(lines)282283    def _format_glossary(self, context: TranslationContext) -> str:284        """Format glossary section."""285        formatted = self.glossary.format_for_prompt(context.glossary_terms)286        return f"\nGLOSSARY (use these terms consistently):\n{formatted}"287288    def _get_best_text(self, verse: Verse) -> str:289        """Get the best available text representation for context."""290        # Prefer English for context (easier for the model to understand)291        if verse.english_kjv:292            return verse.english_kjv293        if verse.english_asv:294            return verse.english_asv295        if verse.japanese_kougo:296            return verse.japanese_kougo297        if verse.japanese_taisho:298            return verse.japanese_taisho299        if verse.japanese_meiji:300            return verse.japanese_meiji301        if verse.greek_text:302            return verse.greek_text303        if verse.hebrew_text:304            return verse.hebrew_text305        return "[text not available]"306307    def get_system_prompt(self) -> str:308        """Get the system prompt for translation.309310        Returns system prompt based on the configured style.311        Uses cached version if style hasn't changed.312313        Returns:314            System prompt string.315        """316        if self._style is None:317            return SYSTEM_PROMPT318319        if self._system_prompt is None:320            self._system_prompt = build_system_prompt(self._style)321322        return self._system_prompt323324    def build_system_prompt(self) -> str:325        """Build system prompt from current style configuration.326327        This always regenerates the prompt (no caching).328329        Returns:330            System prompt string.331        """332        return build_system_prompt(self.style)333334    def build_chunk_prompt(335        self,336        context: ChunkTranslationContext,337        tm_matches: list[TMMatch] | None = None,338        preceding_translations: list[tuple[VerseReference, str]] | None = None,339    ) -> str:340        """Build a chunk/pericope translation prompt.341342        Args:343            context: Full chunk translation context.344            tm_matches: Optional list of translation memory matches.345            preceding_translations: This project's own translations of the346                immediately preceding verses, as (reference, text) pairs —347                the new chunk must continue them seamlessly.348349        Returns:350            Formatted prompt string for translating an entire pericope.351        """352        parts = []353354        # Section header355        pericope = context.pericope356        title = pericope.title or pericope.get_display_range()357        parts.append(f"=== PERICOPE: {context.book.title()} {title} ===")358        if pericope.theme:359            parts.append(f"Theme: {pericope.theme}")360        parts.append("")361362        # Source texts section (all verses)363        parts.append(self._format_chunk_source_texts(context))364365        # Selective morphology per verse (key verb forms)366        chunk_morph_lines = []367        for verse in context.verses:368            notes = _morphology_notes_for(verse, limit=3)369            if notes:370                ref = f"{verse.reference.chapter}:{verse.reference.verse}"371                chunk_morph_lines.append(f"  [{ref}] " + "; ".join(notes))372        if chunk_morph_lines:373            parts.append(374                "\nMORPHOLOGY (parsing of key verb forms — use to disambiguate"375                " tense/aspect, voice, and participles):\n"376                + "\n".join(chunk_morph_lines)377            )378379        # Reference translations for each verse380        parts.append(self._format_chunk_reference_translations(context))381382        # Context section383        parts.append(self._format_chunk_context(context))384385        # Seam continuity: our own translation of the preceding passage386        if preceding_translations:387            seam_lines = [388                "\nPRECEDING TEXT AS ALREADY TRANSLATED (your translation must"389                " read as a seamless continuation of this — match its register,"390                " terminology, and flow):"391            ]392            for ref, text in preceding_translations:393                seam_lines.append(f"  [{ref.chapter}:{ref.verse}] {text}")394            parts.append("\n".join(seam_lines))395396        # Translation Memory section (if matches provided)397        if tm_matches:398            parts.append(self._format_tm_matches(tm_matches))399400        # Glossary section401        if context.glossary_terms:402            parts.append(self._format_chunk_glossary(context))403404        # Translation instruction with JSON output format405        parts.append(self._build_chunk_instruction(context))406407        return "\n".join(parts)408409    def _format_chunk_source_texts(self, context: ChunkTranslationContext) -> str:410        """Format source texts for all verses in chunk."""411        lines = ["SOURCE TEXTS:"]412413        for verse in context.verses:414            ref = f"{verse.reference.chapter}:{verse.reference.verse}"415            if verse.greek_text:416                lines.append(f"[{ref}] {verse.greek_text}")417            elif verse.hebrew_text:418                lines.append(f"[{ref}] {verse.hebrew_text}")419420        return "\n".join(lines)421422    def _format_chunk_reference_translations(423        self, context: ChunkTranslationContext424    ) -> str:425        """Format reference translations for each verse in chunk."""426        lines = ["\nREFERENCE TRANSLATIONS:"]427428        for verse in context.verses:429            ref = f"{verse.reference.chapter}:{verse.reference.verse}"430            lines.append(f"\n[{ref}]")431432            if verse.japanese_meiji:433                lines.append(f"  明治元訳: {verse.japanese_meiji}")434            if verse.japanese_taisho:435                lines.append(f"  大正改訳: {verse.japanese_taisho}")436            if verse.english_kjv:437                lines.append(f"  KJV: {verse.english_kjv}")438439        return "\n".join(lines)440441    def _format_chunk_context(self, context: ChunkTranslationContext) -> str:442        """Format context section for chunk translation."""443        lines = ["\nCONTEXT:"]444445        # Book context446        if context.book_metadata:447            meta = context.book_metadata448            lines.append(449                f"Book: {meta.name_english} ({meta.name_japanese}) - {meta.description}"450            )451452        # Chapter summary453        if context.chapter_summary:454            lines.append(f"Chapter Summary: {context.chapter_summary.summary}")455456        # Preceding context457        if context.preceding_verses:458            lines.append("\nPreceding context:")459            for v in context.preceding_verses:460                ref = f"{v.reference.chapter}:{v.reference.verse}"461                text = self._get_best_text(v)462                lines.append(f"  [{ref}] {text}")463464        # Following context465        if context.following_verses:466            lines.append("\nFollowing context:")467            for v in context.following_verses:468                ref = f"{v.reference.chapter}:{v.reference.verse}"469                text = self._get_best_text(v)470                lines.append(f"  [{ref}] {text}")471472        return "\n".join(lines)473474    def _format_chunk_glossary(self, context: ChunkTranslationContext) -> str:475        """Format glossary section for chunk translation."""476        formatted = self.glossary.format_for_prompt(context.glossary_terms)477        return f"\nGLOSSARY (use these terms consistently):\n{formatted}"478479    def _build_chunk_instruction(self, context: ChunkTranslationContext) -> str:480        """Build the translation instruction with JSON output format."""481        verse_refs = context.pericope.verses482        verse_list = ", ".join(f'"{ref}"' for ref in verse_refs)483484        return f'''485=== TRANSLATION INSTRUCTIONS ===486487Translate all {len(verse_refs)} verses as a COHESIVE UNIT:488- Read the entire pericope first to understand the flow489- Ensure smooth transitions between verses490- Maintain consistent terminology throughout491- Preserve narrative/argument coherence492493OUTPUT FORMAT (JSON):494Return ONLY a JSON object mapping verse references to translations.495No markdown code blocks, no explanations, just the JSON object.496497Example format:498{{499  "1:1": "初めに言葉があった。...",500  "1:2": "この言葉は..."501}}502503Translate verses: [{verse_list}]504505Respond with ONLY the JSON object:'''506507    def get_chunk_system_prompt(self) -> str:508        """Get system prompt for chunk/pericope translation.509510        Uses base style settings but adds chunk-specific instructions.511512        Returns:513            System prompt string for chunk translation.514        """515        base_prompt = self.get_system_prompt()516517        chunk_addition = """518519=== CHUNK TRANSLATION MODE ===520You are translating a PERICOPE (cohesive text unit) rather than individual verses.521522Key principles for pericope translation:5231. Translate with narrative/discourse flow in mind5242. Use transitional words naturally (そして、それで、しかし、etc.)5253. Ensure pronouns have clear antecedents across verses5264. Maintain consistent verb tenses within the unit5275. Consider the entire pericope's theme when choosing vocabulary5286. If a "PRECEDING TEXT AS ALREADY TRANSLATED" section is present, your first529   verse must read as a seamless continuation of it — same register, same530   terminology, natural transition531532Output: JSON object with verse-aligned translations for easy verse lookup.533Each verse should stand alone but flow naturally with adjacent verses."""534535        return base_prompt + chunk_addition536