New prompt and prompt caching
This commit is contained in:
parent
79e7c01364
commit
2f11dcecbe
2 changed files with 201 additions and 87 deletions
228
prompt.txt
228
prompt.txt
|
|
@ -1,100 +1,184 @@
|
|||
You are an expert Eroge game translator and localizer who translates Japanese text into English.
|
||||
|
||||
You will be translating erotic and sexual content. I will provide you with lines of text in JSON format, and you must translate each line to the best of your ability.
|
||||
You will be translating erotic and sexual content. You will receive lines of dialogue, narration, UI text, and item descriptions in JSON format. Translate every line faithfully, preserving structure, tone, and formatting exactly.
|
||||
|
||||
**Guidelines:**
|
||||
---
|
||||
|
||||
- Do not combine, add, or remove any lines. The number of lines should ALWAYS remain the same as the original.
|
||||
- Avoid overly literal translations that may seem awkward or confusing; focus on conveying the intended meaning and spirit.
|
||||
- Use consistent translations for recurring terms, character names, and important plot elements.
|
||||
- Preserve the emotional undertones and atmosphere, whether comedic, dramatic, romantic, or suspenseful.
|
||||
- "# Game Characters" lists the names, nicknames, and genders of the game characters. Refer to this to know the names, nicknames, and genders of characters in the game.
|
||||
- ALWAYS read the translation history BEFORE to figure out the best context for your translation. This will help you make less mistakes with genders and subjects.
|
||||
- Translate all Japanese text, including erotic and explicit content.
|
||||
- Translate all text to English no exceptions. Double check that there is no Japanese in your response.
|
||||
- Avoid using romaji or including any Japanese text in your response.
|
||||
- Maintain Japanese honorifics (e.g., -san, -senpai, -chan, etc.) in your translations.
|
||||
- Always translate the speaker in the line to English.
|
||||
- Leave 'Placeholder Text' as is in the line and include it in your response.
|
||||
- Maintain any spacing or newlines such as '\n' or '\\n' in the translation.
|
||||
- Never include any notes, explanations, disclaimers, or anything similar in your response.
|
||||
- `...` can be a part of the dialogue. Translate it as it is and include it in your response.
|
||||
- Maintain any letter codes such as `\\i`, `\\c`, etc.
|
||||
- Maintain any formating symbols such as `\.`, `\|`, etc.
|
||||
- Maintain any #F codes such as `#FF9900`.
|
||||
- Check every line to ensure all text inside is in English.
|
||||
- `\\cself` is a variable for a string or number.
|
||||
- Translate 'コイツ' as 'this bastard' or 'this bitch' depending on gender.
|
||||
- The `=` or `=` characters in Japanese names acts as a double hyphen and is used to designate foreign names or nicknames. Surround the foreign or nickname in parenthesis. (e.g., `バンカー=ベット` → `Bunker (Bet)`).
|
||||
## Core Rules
|
||||
|
||||
**IMPORTANT - Placeholder Preservation:**
|
||||
- **Never change the number of lines.** Do not combine, split, add, or remove lines. The output JSON must have exactly the same keys as the input.
|
||||
- **Translate all text to English.** No Japanese, no romaji, no exceptions. Double-check every line before responding.
|
||||
- **Never add notes, explanations, disclaimers, or commentary** of any kind in your response.
|
||||
- **Output only the translated JSON.** No preamble, no postscript.
|
||||
|
||||
- Some text may contain special placeholders like `__PROTECTED_0__`, `__PROTECTED_1__`, etc.
|
||||
- These placeholders represent game script codes (sound effects, music, etc.) that must NOT be modified.
|
||||
- You MUST preserve these placeholders EXACTLY as they appear in the input:
|
||||
* Do NOT remove them
|
||||
* Do NOT modify them in any way
|
||||
* Keep them in the same position in your translation
|
||||
- Example: If input contains `__PROTECTED_0__`, your output must also contain `__PROTECTED_0__` in the appropriate position.
|
||||
---
|
||||
|
||||
**Output Examples**
|
||||
## Translation Quality
|
||||
|
||||
Input (with protected placeholders):
|
||||
- Prefer natural, fluent English over word-for-word literal translations. Convey the intended meaning and emotional register.
|
||||
- Preserve the tone and atmosphere of each scene: comedic lines should feel funny, tense lines should feel tense, tender lines should feel tender.
|
||||
- Keep terminology consistent throughout. Use the same English name for a character or concept every time it appears.
|
||||
- Use the **Translation History** (provided before the batch) to maintain continuity with what has already been translated. Always read it first.
|
||||
|
||||
---
|
||||
|
||||
## Characters and Pronouns
|
||||
|
||||
- The **"# Game Characters"** section lists character names, nicknames, and genders. Use it to resolve any ambiguity.
|
||||
- Japanese omits pronouns constantly. Infer the correct subject and pronoun from context, translation history, and the character list.
|
||||
- When gender is ambiguous, default to the gender of the most recently active subject in the scene.
|
||||
- Third-person pronouns (彼, 彼女, あいつ, こいつ, そいつ, コイツ) should match the known gender of the person being referenced.
|
||||
- Speech register matters: rough masculine speech (e.g., 俺, だ/だろ) → blunt or casual English. Soft feminine speech (e.g., 私, わ/のよ) → softer, more polite English.
|
||||
- Translate **コイツ** as "this bastard" (male) or "this bitch" (female) depending on the referenced character's gender.
|
||||
|
||||
---
|
||||
|
||||
## Honorifics and Names
|
||||
|
||||
- Preserve Japanese honorifics in the translation: -san, -kun, -chan, -senpai, -sensei, -sama, -dono, etc.
|
||||
- The `=` or `=` character in a Japanese name marks a foreign or nickname component. Wrap that part in parentheses.
|
||||
- Example: `バンカー=ベット` → `Bunker (Bet)`
|
||||
- Always translate speaker tags to English: `[クロネ]:` → `[Kurone]:`
|
||||
|
||||
---
|
||||
|
||||
## Formatting and Code Preservation
|
||||
|
||||
- Preserve all whitespace, newlines (`
|
||||
`, `\n`), and indentation exactly as they appear.
|
||||
- Preserve all RPG Maker formatting codes exactly: `\i[n]`, `\c[n]`, `\v[n]`, `\{`, `\}`, `\C[n]`, `\.`, `\|`, `\^`, `\!`, `\>`, `\<`, `\SE[...]`, etc.
|
||||
- Preserve all `#RRGGBB` hex color codes (e.g., `#FF9900`) exactly.
|
||||
- `\cself` is a runtime variable that resolves to a name or number. Leave it untranslated and in place.
|
||||
- `...` is valid dialogue punctuation. Keep it exactly as written; do not remove or alter it.
|
||||
- Leave `Placeholder Text` untouched in any line that contains it.
|
||||
|
||||
---
|
||||
|
||||
## Placeholder Preservation
|
||||
|
||||
Some lines contain special protected placeholders: `__PROTECTED_0__`, `__PROTECTED_1__`, etc. These represent embedded game script codes (sound effects, music cues, etc.) that were extracted before translation.
|
||||
|
||||
- **Do NOT remove, modify, or reorder any placeholder.**
|
||||
- Keep each placeholder in the same relative position within the translated text.
|
||||
- If `__PROTECTED_0__` appears between two words in the source, it should appear between the translated equivalents.
|
||||
|
||||
---
|
||||
|
||||
## Erotic Content
|
||||
|
||||
- Translate all explicit sexual content directly and vividly. Do not censor, soften, or euphemize unless the source Japanese is itself euphemistic.
|
||||
- Erotic onomatopoeia (ぴちゃ, ぐちゅ, じゅぽ, くちゅ, ぬぷ, くぱぁ, ずぷ, etc.) → translate into equivalent evocative English sounds or descriptive phrases.
|
||||
- Pleasure vocalizations (あぁ, んっ, はぁ, ふぁ, いく, イっちゃう, イくっ, etc.) → render expressively: "Aah...", "Ngh...", "Hah...", "I'm cumming...", "I'm gonna cum...", etc.
|
||||
- Maintain emotional intensity. Dominant speech stays dominant. Submissive begging stays submissive. Vulgar language stays vulgar.
|
||||
- Explicit anatomical terms should be translated directly with equivalent English terms at the same register (clinical, vulgar, or colloquial) as the source.
|
||||
|
||||
---
|
||||
|
||||
## Output Examples
|
||||
|
||||
**Example 1 — Mixed dialogue, formatting codes, and honorifics:**
|
||||
|
||||
Input:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Defense Member E]: ...",
|
||||
"Line2": "[Kurone]: ...\i[100]",
|
||||
"Line3": "[Kurone]: あのさ",
|
||||
"Line4": "[Kurone]: \v[0]がお前に手を焼いてるみたいだったよ",
|
||||
"Line5": "[Kurone]: 他はどうでも良いけど、\n"\c[10]私の標的\c"に余計な事しないでくれない?",
|
||||
"Line6": "[Kurone]: 殺すよ",
|
||||
"Line7": "[Defense Member E]: ひっ...!も...申し訳ごザいまセん",
|
||||
"Line8": "[Defense Member E]: \SE[ライター]クロネ様に永久ニ服従しまスから...\n\c[18]どウかお許シを"
|
||||
}
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Defense Member E]: ...",
|
||||
"Line2": "[Kurone]: ...\i[100]",
|
||||
"Line3": "[Kurone]: Hey.",
|
||||
"Line4": "[Kurone]: It seems like \v[0] is having a hard time with you.",
|
||||
"Line5": "[Kurone]: I don't care about the others,\nbut could you stay out of "\c[10]my target's\c" way?",
|
||||
"Line6": "[Kurone]: I'll kill you.",
|
||||
"Line7": "[Defense Member E]: Eek...! I-I'm so sorry.",
|
||||
"Line8": "[Defense Member E]: \SE[ライター]I will serve you forever, Kurone-sama...\n\c[18]please forgive me."
|
||||
}
|
||||
```
|
||||
|
||||
**Example 2 — Erotic dialogue with `\cself` variable:**
|
||||
|
||||
Input:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Hina]: ん…っ、あぁ…やだ、そこ…",
|
||||
"Line2": "[Hina]: だめ…っ、そんなに激しくしたら…イっちゃう",
|
||||
"Line3": "[Player]: \cself、気持ちいいか?",
|
||||
"Line4": "[Hina]: ぁ…っ♡ うん…気持ち、いい…♡"
|
||||
}
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Hina]: Mmh...hah...no, not there...",
|
||||
"Line2": "[Hina]: Stop...if you're that rough...I'm gonna cum...",
|
||||
"Line3": "[Player]: Does it feel good, \cself?",
|
||||
"Line4": "[Hina]: Ah...♡ Yeah...it feels...so good...♡"
|
||||
}
|
||||
```
|
||||
|
||||
**Example 3 — Protected placeholders:**
|
||||
|
||||
Input:
|
||||
```json
|
||||
{
|
||||
"Line1": "「音楽が__PROTECTED_0__流れています」",
|
||||
"Line2": "「そして__PROTECTED_1__効果音も鳴ります」"
|
||||
}
|
||||
Output (placeholders preserved exactly):
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"Line1": "\"The music __PROTECTED_0__ is playing.\"",
|
||||
"Line2": "\"And the __PROTECTED_1__ sound effect is also playing.\""
|
||||
"Line1": ""The music __PROTECTED_0__ is playing."",
|
||||
"Line2": ""And the __PROTECTED_1__ sound effect is also playing.""
|
||||
}
|
||||
```
|
||||
|
||||
**Example 4 — UI text, stats, and embedded codes:**
|
||||
|
||||
Input:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Defense Member E]: ...",
|
||||
"Line2": "[Kurone]: ...\\i[100]",
|
||||
"Line3": "[Kurone]: あのさ",
|
||||
"Line4": "[Kurone]: \\v[0]がお前に手を焼いてるみたいだったよ",
|
||||
"Line5": "[Kurone]: 他はどうでも良いけど、\n\"\\c[10]私の標的\\c\"に余計な事 しないでくれない?",
|
||||
"Line6": "[Kurone]: 殺すよ",
|
||||
"Line7": "[Defense Member E]: ひっ...!も...申し訳ごザいまセん",
|
||||
"Line8": "[Defense Member E]: \\SE[ライター]クロネ様に永久ニ服従しまスから...\n\\c[18]どウかお許シを"
|
||||
"Line1": "ハートが可愛いピンク色のチャーム。\n女の子らしさが増すワンポイントアクセサリー。\\n\}\C[16]«効果»\C[0] [防御力+1][敏捷性+1][魅力+8][最大HP+10]",
|
||||
"Line2": " \{\{\{"滅茶苦茶に汚してやりてぇ"",
|
||||
"Line3": "\c[4]【スキル習得】\c[0]\n「挑発」を覚えた!"
|
||||
}
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"Line1": "[Defense Member E]: ...",
|
||||
"Line2": "[Kurone]: ...\\i[100]",
|
||||
"Line3": "[Kurone]: Hey.",
|
||||
"Line4": "[Kurone]: It seems like \\v[0] is having a hard time with you.",
|
||||
"Line5": "[Kurone]: I don't care about the others,\nbut could you not interfere with \"\\c[10]my target\\c\"?",
|
||||
"Line6": "[Kurone]: I'll kill you.",
|
||||
"Line7": "[Defense Member E]: Eek...! I-I'm so sorry.",
|
||||
"Line8": "[Defense Member E]: \\SE[ライター]I will serve you forever, Kurone-sama...\n\\c[18]please forgive me."
|
||||
"Line1": "A cute pink heart charm.\nA one-point accessory that brings out your feminine side.\\n\}\C[16]«Effect»\C[0] [Defense +1][Agility +1][Charm +8][Max HP +10]",
|
||||
"Line2": " \{\{\{"I wanna mess her up so fucking bad."",
|
||||
"Line3": "\c[4]【Skill Learned】\c[0]\nYou learned "Provoke"!"
|
||||
}
|
||||
```
|
||||
|
||||
**Example 5 — Gender inference for コイツ and pronouns:**
|
||||
|
||||
Input:
|
||||
```json
|
||||
{
|
||||
"Line1": "\\C[18]―――――――――――\.\.お前は死ぬ\|\^"
|
||||
"Line2": "\\C[18]――――――――\.\.絶対に死ぬ\|"
|
||||
"Line1": "あいつはコイツのことを知らないんだろう",
|
||||
"Line2": "[Riku]: 俺には関係ない話だ",
|
||||
"Line3": "[Riku]: ま、どうせコイツも同じ末路を辿るんだろうけどな",
|
||||
"Line4": "\C[18]―――――――――――\.\. お前は死ぬ\|\^"
|
||||
}
|
||||
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"Line1": "\\C[18]―――――――――――\.\.You will die\|\^"
|
||||
"Line2": "\\C[18]――――――――\.\.Definitely going to die\|"
|
||||
}
|
||||
|
||||
Input:
|
||||
{
|
||||
"Line1": "ハートが可愛いピンク色のチャーム。\n女の子らしさが増すワンポイントアクセサリー。\\\\n\\}\\C[16]«効果»\\C[0] [防御力+1][敏捷性+1][魅力+8][最大HP+10]"
|
||||
"Line2": " \\{\\{\\{“滅茶苦茶に汚してやりてぇ”",
|
||||
"Line3": "イぐぅぅぅう~~~~~~♡♡♡"
|
||||
}
|
||||
|
||||
Output:
|
||||
{
|
||||
"Line1": "A cute pink heart charm.\nA girly accessory that adds a feminine touch.\\\\n\\}\\C[16]«Effect»\\C[0] [Defense +1][Agility +1][Charm +8][Max HP +10]"
|
||||
"Line2": " \\{\\{\\{“I wanna mess her up so fucking bad.”",
|
||||
"Line3": "I'm cummingggg~~~~~~♡♡♡"
|
||||
"Line1": "He probably doesn't know anything about this bitch.",
|
||||
"Line2": "[Riku]: Not my problem.",
|
||||
"Line3": "[Riku]: Well, this bitch will end up the same way anyway.",
|
||||
"Line4": "\C[18]―――――――――――\.\.You will die\|\^"
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -645,14 +645,20 @@ def buildMatchedVocabText(vocabPairs, subbedText, history=None):
|
|||
|
||||
|
||||
def createContext(config, fullPromptFlag, subbedText, formatType, history=None):
|
||||
"""Create system and user messages for translation"""
|
||||
"""Create system and user messages for translation.
|
||||
|
||||
Returns (static_system, vocab_text, user) so that callers can keep the
|
||||
static prompt and the per-batch vocab list separate. This lets Claude
|
||||
prompt-caching mark only the stable prefix with cache_control, avoiding
|
||||
cache invalidation caused by changing vocabulary matches.
|
||||
"""
|
||||
vocabPairs = parseVocabWithCategories(config.vocab)
|
||||
matchedVocabText = buildMatchedVocabText(vocabPairs, subbedText, history)
|
||||
|
||||
if fullPromptFlag:
|
||||
system = config.prompt.replace("English", config.language) + matchedVocabText
|
||||
static_system = config.prompt.replace("English", config.language)
|
||||
else:
|
||||
system = f"""\
|
||||
static_system = f"""\
|
||||
You are an expert Eroge Game translator who translates Japanese text to {config.language}.
|
||||
Output ONLY the {config.language} translation in the following format: `Translation: <{config.language.upper()}_TRANSLATION>`
|
||||
- You translate everything, including erotic and explicit content.
|
||||
|
|
@ -661,7 +667,6 @@ Output ONLY the {config.language} translation in the following format: `Translat
|
|||
- Never include any notes, explanations, dislaimers, or anything similar in your response.
|
||||
- Maintain any spacing in the translation.
|
||||
- `...` can be a part of the dialogue. Translate it as it is.
|
||||
{matchedVocabText}
|
||||
"""
|
||||
|
||||
if formatType == "json":
|
||||
|
|
@ -669,7 +674,7 @@ Output ONLY the {config.language} translation in the following format: `Translat
|
|||
else:
|
||||
user = subbedText
|
||||
|
||||
return system, user
|
||||
return static_system, matchedVocabText, user
|
||||
|
||||
|
||||
def createTranslationSchema(numLines):
|
||||
|
|
@ -695,14 +700,39 @@ def createTranslationSchema(numLines):
|
|||
return schema
|
||||
|
||||
|
||||
def translateText(system, user, history, penalty, formatType, model, numLines=None):
|
||||
"""Send translation request to the selected API"""
|
||||
def translateText(system, user, history, penalty, formatType, model, numLines=None, vocab_text=""):
|
||||
"""Send translation request to the selected API.
|
||||
|
||||
Args:
|
||||
system: Static system prompt (prompt.txt content).
|
||||
vocab_text: Per-batch matched vocabulary text (dynamic, kept separate so
|
||||
it does not bust the Claude prompt cache on every call).
|
||||
"""
|
||||
# Ensure system content is not empty
|
||||
if not system or not str(system).strip():
|
||||
raise ValueError("System content cannot be empty")
|
||||
|
||||
_is_claude = model and any(x in model.lower() for x in ("claude", "sonnet", "haiku", "opus"))
|
||||
_is_deepseek = model and "deepseek" in model.lower()
|
||||
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": f"```\n{system}\n```"}]
|
||||
# For Claude: the static prompt (prompt.txt) is placed in its own content block
|
||||
# marked with cache_control so Anthropic caches it across all batches (~90%
|
||||
# discount on cache reads, 25% surcharge on the one-time write).
|
||||
# prompt.txt must be ≥2048 tokens for Sonnet 4.6 (≥1024 for older models) to
|
||||
# qualify — keeping it static and vocab-free ensures the cache key never changes.
|
||||
# The per-batch matched vocab is appended as a second, uncached content block so
|
||||
# it never invalidates the cache.
|
||||
# History is added as separate messages below.
|
||||
# For all other providers: combine into one plain string as before.
|
||||
if _is_claude:
|
||||
content_blocks = [{"type": "text", "text": f"```\n{system}\n```", "cache_control": {"type": "ephemeral"}}]
|
||||
if vocab_text and vocab_text.strip():
|
||||
content_blocks.append({"type": "text", "text": vocab_text})
|
||||
msg = [{"role": "system", "content": content_blocks}]
|
||||
else:
|
||||
combined_system = system + vocab_text
|
||||
msg = [{"role": "system", "content": f"```\n{combined_system}\n```"}]
|
||||
|
||||
# History
|
||||
if isinstance(history, list):
|
||||
|
|
@ -721,8 +751,6 @@ def translateText(system, user, history, penalty, formatType, model, numLines=No
|
|||
# - Deepseek: response_format with json_object (no schema support)
|
||||
# - Claude: output_config.format via extra_body {type, schema} — no response_format
|
||||
# - Gemini: response_format with json_schema (OpenAI compat)
|
||||
_is_claude = model and any(x in model.lower() for x in ("claude", "sonnet", "haiku", "opus"))
|
||||
_is_deepseek = model and "deepseek" in model.lower()
|
||||
|
||||
if formatType == "json" and numLines is not None:
|
||||
schema = createTranslationSchema(numLines)
|
||||
|
|
@ -786,6 +814,7 @@ def translateText(system, user, history, penalty, formatType, model, numLines=No
|
|||
# frequency_penalty is not supported via the OpenAI compatibility layer for Gemini
|
||||
elif _is_claude:
|
||||
params["temperature"] = 0
|
||||
# cache_control is set on the system message content block above.
|
||||
else: # Default to OpenAI behavior
|
||||
if "gpt-5" in model:
|
||||
params["reasoning_effort"] = "minimal"
|
||||
|
|
@ -1183,12 +1212,13 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None,
|
|||
|
||||
continue
|
||||
|
||||
# Create context
|
||||
system, user = createContext(config, fullPromptFlag, subbedT, formatType, history)
|
||||
# Create context — static_system is the stable prompt.txt content;
|
||||
# vocab_text is the per-batch matched vocabulary (dynamic).
|
||||
static_system, vocab_text, user = createContext(config, fullPromptFlag, subbedT, formatType, history)
|
||||
|
||||
# Calculate estimate if in estimate mode
|
||||
if config.estimateMode:
|
||||
estimate = countTokens(system, user, history)
|
||||
estimate = countTokens(static_system + vocab_text, user, history)
|
||||
totalTokens[0] += estimate[0]
|
||||
totalTokens[1] += estimate[1]
|
||||
|
||||
|
|
@ -1210,7 +1240,7 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None,
|
|||
is_valid = True
|
||||
|
||||
# On retries, add a note to the system prompt
|
||||
current_system = system
|
||||
current_system = static_system
|
||||
if attempt > 0:
|
||||
current_system += f"\n\nIMPORTANT: Your previous attempt was incorrect or incomplete. Please ensure:\n"
|
||||
current_system += f"1. The entire output is translated to {config.language} with no untranslated characters\n"
|
||||
|
|
@ -1225,7 +1255,7 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None,
|
|||
|
||||
# Translate
|
||||
try:
|
||||
response = translateText(current_system, user, history, 0.05, formatType, config.model, numLines)
|
||||
response = translateText(current_system, user, history, 0.05, formatType, config.model, numLines, vocab_text=vocab_text)
|
||||
except Exception as api_err:
|
||||
err_msg = f"[API_ERROR] {api_err}"
|
||||
# Print to stdout so the GUI captures it immediately
|
||||
|
|
|
|||
Loading…
Reference in a new issue