fix(workflow): scope game tools and externalize prompts
- Install UberWolf CLI only for detected WOLF games - Move static clipboard prompts into editable shipped skills - Improve plugin and Ace translation audit workflows - Add regression coverage for engine filtering and prompt loading
This commit is contained in:
parent
2e7f816ecd
commit
38ca8e44be
17 changed files with 632 additions and 424 deletions
|
|
@ -29,6 +29,11 @@ Edit tool-level markdown:
|
||||||
|
|
||||||
- `data/skills/system.md` - base translation system prompt
|
- `data/skills/system.md` - base translation system prompt
|
||||||
- `data/skills/project_setup.md` - clipboard skill for IDE setup
|
- `data/skills/project_setup.md` - clipboard skill for IDE setup
|
||||||
|
- `data/skills/wrap_config.md` - RPG Maker wrap-width analysis prompt
|
||||||
|
- `data/skills/plugin_translation.md` - MV/MZ plugin translation prompt
|
||||||
|
- `data/skills/ace_script_translation.md` - VX Ace Ruby script translation prompt
|
||||||
|
- `data/skills/risky_codes.md` - RPG Maker optional event-code audit prompt
|
||||||
|
- `data/skills/wolf_speakers.md` - WOLF speaker-format audit prompt
|
||||||
- `data/translation_contexts.json` - per-call history templates
|
- `data/translation_contexts.json` - per-call history templates
|
||||||
|
|
||||||
Per-game quirks / Translation Frame live under `<game>/skills/` and are edited in Workflow Step 2, not here.
|
Per-game quirks / Translation Frame live under `<game>/skills/` and are edited in Workflow Step 2, not here.
|
||||||
|
|
|
||||||
44
data/skills/ace_script_translation.md
Normal file
44
data/skills/ace_script_translation.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
You are an expert RPG Maker VX Ace Ruby/RGSS3 localisation engineer working inside an IDE with access to this game project.
|
||||||
|
|
||||||
|
<goal>
|
||||||
|
Audit ace_json/scripts/*.rb for player-visible Japanese, tell me which scripts need localisation, and—only after I approve—translate the safe items directly in those files without breaking game logic.
|
||||||
|
</goal>
|
||||||
|
|
||||||
|
<inputs>
|
||||||
|
- Read vocab.txt and the extracted Ruby files under ace_json/scripts/.
|
||||||
|
- Use any script index/manifest to preserve script names and ordering.
|
||||||
|
- Treat vocab.txt as authoritative for names and terminology.
|
||||||
|
- If referenced script sources are missing or still packed, list what is needed and ask me to extract or provide them.
|
||||||
|
</inputs>
|
||||||
|
|
||||||
|
<phase_1_audit_only>
|
||||||
|
Do not edit anything yet. Return a compact table with:
|
||||||
|
- Script/file
|
||||||
|
- Approximate count of player-visible Japanese strings
|
||||||
|
- 1-3 short examples with line references
|
||||||
|
- Classification: Easy/safe, Needs review, or No translation needed
|
||||||
|
- Why it appears visible or risky
|
||||||
|
|
||||||
|
Then ask one focused question: which listed scripts should you translate? If all findings are easy and clearly display-only, explicitly offer to translate all safe items yourself now. Do not ask me to inspect every string manually. If nothing needs work, say so and stop.
|
||||||
|
</phase_1_audit_only>
|
||||||
|
|
||||||
|
<translate_only_when_approved>
|
||||||
|
Translate only literals proven to reach the player through windows, draw_text/help methods, messages, menus, battle logs, or notifications. A string passed to print or p is not automatically player-visible; verify its runtime use.
|
||||||
|
|
||||||
|
Never translate hash keys, symbols, identifiers, class/module/method/constant names, lookup values, case/when or equality operands, filenames, paths, URLs, fonts, colors, regexes, save-data keys, or text read back elsewhere. Preserve Ruby interpolation (#{...}), printf/sprintf placeholders, escape sequences, control codes, quote style, encoding, and syntax. Search usages before changing an ambiguous literal; skip it if safety cannot be established.
|
||||||
|
|
||||||
|
Edit approved .rb files directly and make the smallest possible changes. Validate Ruby syntax when a suitable local check exists, and preserve the extracted script layout so RV2JSON can repack it.
|
||||||
|
</translate_only_when_approved>
|
||||||
|
|
||||||
|
<response_rules>
|
||||||
|
Never paste or repost an entire script. After edits, report only:
|
||||||
|
- Files/scripts changed and number of strings translated
|
||||||
|
- A few representative before -> after examples with line references
|
||||||
|
- Skipped ambiguous strings and the reason
|
||||||
|
- Validation performed
|
||||||
|
|
||||||
|
If direct file editing is unavailable, provide only a minimal unified diff or targeted replacements—never complete file contents.
|
||||||
|
</response_rules>
|
||||||
|
|
||||||
|
Start with the audit and approval question. Do not translate yet.
|
||||||
|
|
||||||
44
data/skills/plugin_translation.md
Normal file
44
data/skills/plugin_translation.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
You are an expert RPG Maker MV/MZ localisation engineer working inside an IDE with access to this game project.
|
||||||
|
|
||||||
|
<goal>
|
||||||
|
Audit enabled plugins for player-visible Japanese, tell me which plugins need localisation, and—only after I approve—translate the safe items directly in the project files without breaking plugin behavior.
|
||||||
|
</goal>
|
||||||
|
|
||||||
|
<inputs>
|
||||||
|
- Read js/plugins.js and vocab.txt.
|
||||||
|
- For every enabled entry in js/plugins.js, inspect its parameter values.
|
||||||
|
- If available, also inspect the matching js/plugins/<PluginName>.js source file for visible Japanese literals. If a required source file is missing, list it and ask me to provide it.
|
||||||
|
- Treat vocab.txt as authoritative for names and terminology.
|
||||||
|
</inputs>
|
||||||
|
|
||||||
|
<phase_1_audit_only>
|
||||||
|
Do not edit anything yet. Return a compact table with:
|
||||||
|
- Plugin and file
|
||||||
|
- Approximate count of player-visible Japanese strings
|
||||||
|
- 1-3 short examples with line/parameter references
|
||||||
|
- Classification: Easy/safe, Needs review, or No translation needed
|
||||||
|
- Why it appears visible or risky
|
||||||
|
|
||||||
|
Then ask one focused question: which listed plugins should you translate? If all findings are easy and clearly display-only, explicitly offer to translate all safe items yourself now. Do not ask me to inspect every string manually. If nothing needs work, say so and stop.
|
||||||
|
</phase_1_audit_only>
|
||||||
|
|
||||||
|
<translate_only_when_approved>
|
||||||
|
Translate only strings proven to be displayed to the player, including UI labels, window titles, help text, notifications, battle messages, and visible plugin parameters.
|
||||||
|
|
||||||
|
Never translate plugin names, parameter/property keys, identifiers, lookup values, plugin commands, switch/variable names, filenames, paths, URLs, fonts, color codes, regexes, booleans, numeric strings, or text compared/read back elsewhere. Preserve placeholders, escapes, control codes, interpolation, whitespace, encoding, and JavaScript syntax. Search usages before changing an ambiguous literal; skip it if safety cannot be established.
|
||||||
|
|
||||||
|
Edit approved files directly and make the smallest possible changes. Validate syntax when a suitable local check exists.
|
||||||
|
</translate_only_when_approved>
|
||||||
|
|
||||||
|
<response_rules>
|
||||||
|
Never paste or repost an entire file. After edits, report only:
|
||||||
|
- Files/plugins changed and number of strings translated
|
||||||
|
- A few representative before -> after examples with line/parameter references
|
||||||
|
- Skipped ambiguous strings and the reason
|
||||||
|
- Validation performed
|
||||||
|
|
||||||
|
If direct file editing is unavailable, provide only a minimal unified diff or targeted replacements—never complete file contents.
|
||||||
|
</response_rules>
|
||||||
|
|
||||||
|
Start with the audit and approval question. Do not translate yet.
|
||||||
|
|
||||||
145
data/skills/risky_codes.md
Normal file
145
data/skills/risky_codes.md
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
You are an expert RPGMaker MV/MZ event system analyst.
|
||||||
|
|
||||||
|
<task>
|
||||||
|
Audit several optional event code types and report: (a) which contain player-visible Japanese text that needs translation, and (b) for code 122, exactly which variable ID ranges carry display text versus internal keys.
|
||||||
|
</task>
|
||||||
|
|
||||||
|
--- attach Actors.json, CommonEvents.json, Troops.json, Map*.json, and js/plugins.js here ---
|
||||||
|
|
||||||
|
<actor_context>
|
||||||
|
Read Actors.json in full before auditing event commands. Use it to resolve actor IDs, \N[n] references, and code 320/324/325 targets. If you see an event reference to a major actor whose full character vocab is missing or only appears as a placeholder mapping like \N[3] (Keimi), report that Actors.json should be included in the glossary build and that a full # Game Characters entry is needed.
|
||||||
|
</actor_context>
|
||||||
|
|
||||||
|
<audit_1 code="122">
|
||||||
|
Code 122 — Control Variables — which variable IDs carry display text
|
||||||
|
|
||||||
|
A code 122 entry looks like:
|
||||||
|
{ "code": 122, "parameters": [startVarId, endVarId, 0, 4, "\"some string\""] }
|
||||||
|
parameters[0] is the variable ID being set; parameters[4] is the string value
|
||||||
|
(only present when parameters[3] == 4, direct string assignment).
|
||||||
|
|
||||||
|
Collect every code 122 where parameters[3] == 4. For each variable ID found:
|
||||||
|
1. Is the string also tested in a code 111 $gameVariables comparison?
|
||||||
|
→ DO NOT TRANSLATE (would break game logic)
|
||||||
|
2. Is the string used as an internal ID / plugin key / script argument?
|
||||||
|
→ DO NOT TRANSLATE
|
||||||
|
3. Is the string purely player-visible display text?
|
||||||
|
→ SAFE TO TRANSLATE
|
||||||
|
|
||||||
|
Summarise translatable variable IDs as compact numeric ranges,
|
||||||
|
e.g. 'Translate IDs: 5, 10-18, 42'. Separately list any DO NOT TRANSLATE IDs
|
||||||
|
with the reason. The ranges will be entered as min/max in the translation tool.
|
||||||
|
</audit_1>
|
||||||
|
|
||||||
|
<audit_2>
|
||||||
|
Plugin codes with visible text — 357 / 356 / 355-655 / 657 / 320 / 324 / 325 / 108
|
||||||
|
For each code type below, scan all events and report whether any instance contains Japanese text visible to the player at runtime.
|
||||||
|
|
||||||
|
--- Code 357 (MZ Plugin Commands) ---
|
||||||
|
parameters[0] = plugin header; parameters[3] = data dict.
|
||||||
|
Look up each unique header in js/plugins.js.
|
||||||
|
Does any key in parameters[3] hold Japanese text shown on screen?
|
||||||
|
The module already handles: AdvExtentionllk, VisuMZ_4_ProximityMessages, LL_GalgeChoiceWindow
|
||||||
|
For other headers with visible text, check the commented-out headerMappings list:
|
||||||
|
"LL_InfoPopupWIndow": (["messageText"], None),
|
||||||
|
"QuestSystem": (["DetailNote"], None),
|
||||||
|
"BalloonInBattle": (["text"], None),
|
||||||
|
"MNKR_CommonPopupCoreMZ": (["text"], None),
|
||||||
|
"DestinationWindow": (["destination"], None),
|
||||||
|
"_TMLogWindowMZ": (["text"], None),
|
||||||
|
"TorigoyaMZ_NotifyMessage": (["message"], None),
|
||||||
|
"SoR_GabWindow": (["arg1"], None),
|
||||||
|
"DarkPlasma_CharacterText": (["text"], None),
|
||||||
|
"DTextPicture": (["text"], None),
|
||||||
|
"TextPicture": (["text"], None),
|
||||||
|
"TRP_SkitMZ": (["name"], None),
|
||||||
|
"LogWindow": (["text"], None),
|
||||||
|
"BattleLogOutput": (["message"], None),
|
||||||
|
"TorigoyaMZ_NotifyMessage_CommandMessage": (["message"], None),
|
||||||
|
"NUUN_SaveScreen": (["AnyName"], None),
|
||||||
|
"build/ARPG_Core": (["Text", "SkillByName"], None),
|
||||||
|
Output: EXACT uncomment line, or describe parameters[3] if header is new.
|
||||||
|
|
||||||
|
--- Code 356 (MV Plugin Commands) ---
|
||||||
|
parameters[0] is a space-delimited string, e.g. 'D_TEXT テキスト 24'.
|
||||||
|
The module already handles: D_TEXT, ShowInfo, PushGab, addLog, DW_*, CommonPopup, AddCustomChoice.
|
||||||
|
Does any 356 line have Japanese that is shown on screen?
|
||||||
|
If yes: ENABLE CODE356 and list the command keywords found.
|
||||||
|
If no: SKIP CODE356.
|
||||||
|
|
||||||
|
--- Code 355/655 (Inline Scripts) ---
|
||||||
|
Code 355 starts a script block; code 655 continues it.
|
||||||
|
parameters[0] is the raw JS/script text.
|
||||||
|
For each block with Japanese in a string passed to a message/popup/log function:
|
||||||
|
• The leading keyword/pattern that identifies the block
|
||||||
|
• A regex capturing only the display substring, e.g. テキスト-(.+)
|
||||||
|
• Whether it is single-line (355 only) or multi-line (355 + 655)
|
||||||
|
• The exact entry to add to the patterns dict:
|
||||||
|
"<keyword>": (r"<regex>", <True|False>),
|
||||||
|
|
||||||
|
--- Code 657 (Picture Text) ---
|
||||||
|
parameters[0] is a plain string drawn onto a picture.
|
||||||
|
Does any 657 entry contain Japanese text visible on screen (not a filename)?
|
||||||
|
If yes: ENABLE CODE657. If no: SKIP CODE657.
|
||||||
|
|
||||||
|
--- Codes 320 / 324 / 325 (Actor Name / Nickname / Profile) ---
|
||||||
|
parameters[1] is the new name/nickname/profile string.
|
||||||
|
Do any of these codes set Japanese strings visible to the player
|
||||||
|
(not just internal IDs or filenames)?
|
||||||
|
If yes: ENABLE the respective code. If no: SKIP.
|
||||||
|
|
||||||
|
--- Code 108 (Comment / Notetag) ---
|
||||||
|
parameters[0] is a comment string used as a plugin notetag.
|
||||||
|
The module only translates 108 lines that match specific patterns:
|
||||||
|
info:, ActiveMessage:, event_text, Menu Name, text_indicator, NW名前指定
|
||||||
|
Do any 108 entries matching those patterns have Japanese visible to the player?
|
||||||
|
If yes: ENABLE CODE108 and list the patterns found. If no: SKIP CODE108.
|
||||||
|
</audit_2>
|
||||||
|
|
||||||
|
<output_format>
|
||||||
|
Reply with these exact sections:
|
||||||
|
|
||||||
|
### Code 122 — Variable Translation Ranges
|
||||||
|
Translate IDs : <compact ranges, e.g. 5, 10-18, 42>
|
||||||
|
Do NOT translate: <ID — reason>
|
||||||
|
If none: 'No display-text string assignments found.'
|
||||||
|
|
||||||
|
### Code 357 — Plugin Handlers to Enable
|
||||||
|
• Already handled : <header> — no action needed
|
||||||
|
• Enable in module : uncomment → "<header>": (["<key>"], None),
|
||||||
|
• New entry needed : <header> — parameters[3] structure: <description>
|
||||||
|
• No visible text : <header> — internal only, skip
|
||||||
|
If none: 'No code 357 visible text found.'
|
||||||
|
|
||||||
|
### Code 356 — Enable or Skip
|
||||||
|
ENABLE / SKIP — keywords found: <list>
|
||||||
|
|
||||||
|
### Code 355/655 — Script Patterns to Add
|
||||||
|
• Pattern key : <keyword>
|
||||||
|
• Regex : <capture regex>
|
||||||
|
• Multiline : <true/false>
|
||||||
|
• Module line : "<keyword>": (r"<regex>", <True|False>),
|
||||||
|
If none: 'No translatable 355/655 scripts found.'
|
||||||
|
|
||||||
|
### Code 657 — Enable or Skip
|
||||||
|
ENABLE / SKIP — <brief reason>
|
||||||
|
|
||||||
|
### Codes 320 / 324 / 325 — Enable or Skip
|
||||||
|
320: ENABLE / SKIP 324: ENABLE / SKIP 325: ENABLE / SKIP
|
||||||
|
|
||||||
|
### Code 108 — Enable or Skip
|
||||||
|
ENABLE / SKIP — patterns found: <list>
|
||||||
|
|
||||||
|
### GUI Action Summary
|
||||||
|
|
||||||
|
After all audit sections above, output this final block to configure the GUI:
|
||||||
|
|
||||||
|
ENABLE CODES : <comma-separated CODE* keys to check, e.g. CODE357, CODE356>
|
||||||
|
SKIP CODES : <codes that have no visible text>
|
||||||
|
357 PLUGINS : <comma-separated HEADER_MAPPINGS_357 keys to enable>
|
||||||
|
355/655 PATTERNS : <comma-separated PATTERNS_355655 keys to enable>
|
||||||
|
122 VAR RANGE : min=<N>, max=<M>
|
||||||
|
|
||||||
|
If a field has nothing to fill in, write NONE.
|
||||||
|
</output_format>
|
||||||
|
|
||||||
26
data/skills/wolf_speakers.md
Normal file
26
data/skills/wolf_speakers.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
You are an expert Japanese WOLF RPG Editor translator helping configure a translation tool.
|
||||||
|
|
||||||
|
<background>
|
||||||
|
WolfDawn extracts each line with a 'speaker' and a 'speaker_src' tag describing how it detected the speaker. Two tags carry the speaker name baked into the FIRST line of the 'source' text (the rest is the dialogue body):
|
||||||
|
- literal_line1 : HIGH confidence - a face/name window precedes the line, so the first line really is a nameplate. The tool always reshapes these; nothing to decide.
|
||||||
|
- literal_line1_lowconf : LOW confidence - a short first line with NO preceding face window. WolfDawn guesses it is a speaker name, but it might actually be the start of the dialogue or narration.
|
||||||
|
When a first-line format is trusted, the tool reshapes 'Name\nbody' into '[Name]: body' for translation, then restores 'Name\nbody' on inject (byte-safe either way).
|
||||||
|
</background>
|
||||||
|
|
||||||
|
--- attach the extracted JSON in files/ here (maps / CommonEvent) before continuing ---
|
||||||
|
|
||||||
|
<task>
|
||||||
|
Decide whether the LOW-confidence first-line guesses (speaker_src = literal_line1_lowconf) should be treated as speaker names for THIS game. Inspect a good sample of those entries in files/: look at the first line of each such 'source' and judge whether it is genuinely a character/speaker label as opposed to real dialogue or narration.
|
||||||
|
- ENABLE if the low-confidence first lines are overwhelmingly real speaker names.
|
||||||
|
- DISABLE if many are actually dialogue/narration (reshaping them would mislabel lines).
|
||||||
|
(The high-confidence nameplates are always handled; you are only ruling on the guesses.)
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<output_format>
|
||||||
|
Return ONLY a fenced code block containing the recommendation and a one-line reason, e.g.
|
||||||
|
```
|
||||||
|
LOWCONF_FIRSTLINE: ENABLE - low-confidence first lines are short character names (市民, 兵士...).
|
||||||
|
```
|
||||||
|
Use exactly ENABLE or DISABLE after the colon.
|
||||||
|
</output_format>
|
||||||
|
|
||||||
32
data/skills/wrap_config.md
Normal file
32
data/skills/wrap_config.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
You are an expert RPGMaker MV/MZ configuration analyst.
|
||||||
|
|
||||||
|
<task>
|
||||||
|
Calculate the correct text-wrap width settings for a Japanese-to-English RPGMaker MV/MZ translation tool. The tool wraps translated English using character-count limits (not pixels). I need three values: width, listWidth, and noteWidth.
|
||||||
|
</task>
|
||||||
|
|
||||||
|
--- attach System.json and js/plugins.js here before continuing ---
|
||||||
|
|
||||||
|
<instructions>
|
||||||
|
1. Read screenWidth and fontSize from System.json.
|
||||||
|
Check js/plugins.js for any MessageCore or Window plugin that overrides these values.
|
||||||
|
2. For each window type, estimate its pixel width, subtract ~48px padding, then calculate:
|
||||||
|
chars = floor(content_px / (font_size × 0.58))
|
||||||
|
- width: main dialogue/message box (Show Text) — typically full screen width
|
||||||
|
- listWidth: item/skill/help description windows — typically full or half screen width
|
||||||
|
- noteWidth: database note fields — typically the narrowest pane (~40–50% screen width)
|
||||||
|
3. If font size is above 26px and reducing it would meaningfully increase characters per line, note where to change it (System.json or the relevant plugin parameter).
|
||||||
|
</instructions>
|
||||||
|
|
||||||
|
<output_format>
|
||||||
|
Output only the final values — do not show calculations:
|
||||||
|
|
||||||
|
```
|
||||||
|
width=<N>
|
||||||
|
listWidth=<N>
|
||||||
|
noteWidth=<N>
|
||||||
|
fontSize=<N> # or: no change needed
|
||||||
|
```
|
||||||
|
|
||||||
|
Followed by one sentence of assumptions if anything was estimated.
|
||||||
|
</output_format>
|
||||||
|
|
||||||
|
|
@ -376,6 +376,21 @@ function Test-WolfLooseDataReady {
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-WolfGameRoot {
|
||||||
|
param([Parameter(Mandatory = $true)][string]$Root)
|
||||||
|
foreach ($name in @('Data.wolf', 'data.wolf')) {
|
||||||
|
if (Test-Path -LiteralPath (Join-Path $Root $name) -PathType Leaf) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($name in @('Data', 'data')) {
|
||||||
|
if (Test-WolfLooseDataReady -DataDir (Join-Path $Root $name)) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
function Find-UberWolfCli {
|
function Find-UberWolfCli {
|
||||||
param([Parameter(Mandatory = $true)][string]$Root)
|
param([Parameter(Mandatory = $true)][string]$Root)
|
||||||
$candidates = @(
|
$candidates = @(
|
||||||
|
|
@ -849,10 +864,13 @@ function Invoke-PatchDownloadExtract {
|
||||||
throw ('PATCH_ERR:ZIP:Expected one root folder in archive, found {0}.' -f $dirs.Count)
|
throw ('PATCH_ERR:ZIP:Expected one root folder in archive, found {0}.' -f $dirs.Count)
|
||||||
}
|
}
|
||||||
$stagedRoot = $dirs[0].FullName
|
$stagedRoot = $dirs[0].FullName
|
||||||
|
$isWolfGame = Test-WolfGameRoot -Root $Root
|
||||||
|
$wolfOnlyNames = @('UberWolfCli.exe', 'UberWolfCli.LICENSE.txt')
|
||||||
|
|
||||||
# Stage UberWolfCli into the game root before unpack so first-time
|
if ($isWolfGame) {
|
||||||
# players can convert Data.wolf -> Data/ before English files land.
|
# Stage UberWolfCli before unpack so packed WOLF games can
|
||||||
foreach ($cliName in @('UberWolfCli.exe', 'UberWolfCli.LICENSE.txt')) {
|
# convert Data.wolf -> Data/ before English files land.
|
||||||
|
foreach ($cliName in $wolfOnlyNames) {
|
||||||
$stagedCli = Join-Path $stagedRoot $cliName
|
$stagedCli = Join-Path $stagedRoot $cliName
|
||||||
$destCli = Join-Path $Root $cliName
|
$destCli = Join-Path $Root $cliName
|
||||||
if ((Test-Path -LiteralPath $stagedCli -PathType Leaf) -and
|
if ((Test-Path -LiteralPath $stagedCli -PathType Leaf) -and
|
||||||
|
|
@ -861,8 +879,26 @@ function Invoke-PatchDownloadExtract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Invoke-WolfPreSetup -Root $Root
|
Invoke-WolfPreSetup -Root $Root
|
||||||
|
}
|
||||||
|
|
||||||
Copy-Item -Path (Join-Path $stagedRoot '*') -Destination $PWD.Path -Recurse -Force
|
# Copy file-by-file so WOLF-only tools never leak into RPG Maker,
|
||||||
|
# SRPG, or other game roots through a downloaded patch archive.
|
||||||
|
$sourcePrefix = $stagedRoot.TrimEnd(
|
||||||
|
[IO.Path]::DirectorySeparatorChar,
|
||||||
|
[IO.Path]::AltDirectorySeparatorChar
|
||||||
|
) + [IO.Path]::DirectorySeparatorChar
|
||||||
|
foreach ($file in Get-ChildItem -LiteralPath $stagedRoot -Recurse -File -Force) {
|
||||||
|
if ((-not $isWolfGame) -and ($file.Name -in $wolfOnlyNames)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$relative = $file.FullName.Substring($sourcePrefix.Length)
|
||||||
|
$destination = Join-Path $Root $relative
|
||||||
|
$destinationDir = Split-Path -Parent $destination
|
||||||
|
if (-not (Test-Path -LiteralPath $destinationDir -PathType Container)) {
|
||||||
|
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
Copy-Item -LiteralPath $file.FullName -Destination $destination -Force
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (Test-Path -LiteralPath $zipPath) {
|
if (Test-Path -LiteralPath $zipPath) {
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,17 @@ wolf_loose_data_ready() {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_wolf_game() {
|
||||||
|
local name data_dir
|
||||||
|
for name in Data.wolf data.wolf; do
|
||||||
|
[ -f "$ROOT_DIR/$name" ] && return 0
|
||||||
|
done
|
||||||
|
for data_dir in "$ROOT_DIR/Data" "$ROOT_DIR/data"; do
|
||||||
|
wolf_loose_data_ready "$data_dir" && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
find_uberwolf_cli() {
|
find_uberwolf_cli() {
|
||||||
if [ -f "$ROOT_DIR/UberWolfCli.exe" ]; then
|
if [ -f "$ROOT_DIR/UberWolfCli.exe" ]; then
|
||||||
echo "$ROOT_DIR/UberWolfCli.exe"
|
echo "$ROOT_DIR/UberWolfCli.exe"
|
||||||
|
|
@ -389,6 +400,9 @@ download_extract() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
wolf_patch=0
|
||||||
|
if is_wolf_game; then
|
||||||
|
wolf_patch=1
|
||||||
# Stage UberWolfCli before unpack so Data.wolf -> Data/ happens before
|
# Stage UberWolfCli before unpack so Data.wolf -> Data/ happens before
|
||||||
# English patch files are copied on top.
|
# English patch files are copied on top.
|
||||||
for cli_name in UberWolfCli.exe UberWolfCli.LICENSE.txt; do
|
for cli_name in UberWolfCli.exe UberWolfCli.LICENSE.txt; do
|
||||||
|
|
@ -397,9 +411,24 @@ download_extract() {
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
invoke_wolf_pre_setup
|
invoke_wolf_pre_setup
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Applying patch..."
|
echo "Applying patch..."
|
||||||
if ! cp -rf "$inner"/* "$ROOT_DIR/"; then
|
copy_failed=0
|
||||||
|
while IFS= read -r -d '' patch_file; do
|
||||||
|
relative_path="${patch_file#"$inner"/}"
|
||||||
|
file_name="${patch_file##*/}"
|
||||||
|
if [ "$wolf_patch" -ne 1 ] && \
|
||||||
|
{ [ "$file_name" = "UberWolfCli.exe" ] || [ "$file_name" = "UberWolfCli.LICENSE.txt" ]; }; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
target_path="$ROOT_DIR/$relative_path"
|
||||||
|
if ! mkdir -p "$(dirname "$target_path")" || ! cp -f "$patch_file" "$target_path"; then
|
||||||
|
copy_failed=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < <(find "$inner" -type f -print0)
|
||||||
|
if [ "$copy_failed" -ne 0 ]; then
|
||||||
echo "Patch application failed!"
|
echo "Patch application failed!"
|
||||||
rm -rf "$TMP_EX"
|
rm -rf "$TMP_EX"
|
||||||
rm -f "$ROOT_DIR/repo.zip"
|
rm -f "$ROOT_DIR/repo.zip"
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,41 @@ class SkillsTab(QWidget):
|
||||||
),
|
),
|
||||||
is_json=False,
|
is_json=False,
|
||||||
)
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="wrap_config",
|
||||||
|
tab_title="Wrap Config",
|
||||||
|
path=SKILLS_DIR / "wrap_config.md",
|
||||||
|
hint="Clipboard prompt that asks the IDE to calculate RPG Maker wrap widths.",
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="plugin_translation",
|
||||||
|
tab_title="Plugin TL",
|
||||||
|
path=SKILLS_DIR / "plugin_translation.md",
|
||||||
|
hint="MV/MZ plugin localisation audit and approved in-place translation prompt.",
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="ace_script_translation",
|
||||||
|
tab_title="Ace Script TL",
|
||||||
|
path=SKILLS_DIR / "ace_script_translation.md",
|
||||||
|
hint="VX Ace Ruby script localisation audit and approved in-place translation prompt.",
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="risky_codes",
|
||||||
|
tab_title="Risky Codes",
|
||||||
|
path=SKILLS_DIR / "risky_codes.md",
|
||||||
|
hint="Clipboard prompt for auditing optional RPG Maker event-code translation settings.",
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="wolf_speakers",
|
||||||
|
tab_title="WOLF Speakers",
|
||||||
|
path=SKILLS_DIR / "wolf_speakers.md",
|
||||||
|
hint="Clipboard prompt for reviewing WOLF low-confidence first-line speakers.",
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
self._add_file_page(
|
self._add_file_page(
|
||||||
key="contexts",
|
key="contexts",
|
||||||
tab_title="Contexts",
|
tab_title="Contexts",
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ from util.project_scanner import (
|
||||||
wolf_repair_nested_data_dir,
|
wolf_repair_nested_data_dir,
|
||||||
wolf_unpack_out_dir,
|
wolf_unpack_out_dir,
|
||||||
)
|
)
|
||||||
from util.skills import load_project_setup
|
from util.skills import load_clipboard_skill, load_project_setup
|
||||||
|
|
||||||
# Workflow-level label for the non-batch (live) translation path. The Translation
|
# Workflow-level label for the non-batch (live) translation path. The Translation
|
||||||
# tab's own mode is called "Translate"; _workflow_mode_text() maps to that.
|
# tab's own mode is called "Translate"; _workflow_mode_text() maps to that.
|
||||||
|
|
@ -129,47 +129,7 @@ WORK_DIR_NAME = "wolf_json"
|
||||||
PHASE_NAMES_KINDS = {"names"}
|
PHASE_NAMES_KINDS = {"names"}
|
||||||
PHASE_DB_KINDS = {"db"}
|
PHASE_DB_KINDS = {"db"}
|
||||||
PHASE_MAPS_EVENTS_KINDS = {"map", "common", "gamedat", "txt", "txt-dir"}
|
PHASE_MAPS_EVENTS_KINDS = {"map", "common", "gamedat", "txt", "txt-dir"}
|
||||||
|
# The speaker-format prompt is loaded from editable data/skills/wolf_speakers.md.
|
||||||
# Speaker-format prompt for a repo-aware AI. WolfDawn already detects and tags who
|
|
||||||
# speaks on each line, so the only decision left is whether its LOW-confidence
|
|
||||||
# first-line guesses are really speaker names for this game. The AI inspects the
|
|
||||||
# extracted text and returns a single ENABLE/DISABLE recommendation.
|
|
||||||
_WOLF_SPEAKER_PROMPT = (
|
|
||||||
"You are an expert Japanese WOLF RPG Editor translator helping configure a translation tool.\n"
|
|
||||||
"\n"
|
|
||||||
"<background>\n"
|
|
||||||
"WolfDawn extracts each line with a 'speaker' and a 'speaker_src' tag describing how it "
|
|
||||||
"detected the speaker. Two tags carry the speaker name baked into the FIRST line of the "
|
|
||||||
"'source' text (the rest is the dialogue body):\n"
|
|
||||||
" - literal_line1 : HIGH confidence - a face/name window precedes the line, so the "
|
|
||||||
"first line really is a nameplate. The tool always reshapes these; nothing to decide.\n"
|
|
||||||
" - literal_line1_lowconf : LOW confidence - a short first line with NO preceding face "
|
|
||||||
"window. WolfDawn guesses it is a speaker name, but it might actually be the start of the "
|
|
||||||
"dialogue or narration.\n"
|
|
||||||
"When a first-line format is trusted, the tool reshapes 'Name\\nbody' into '[Name]: body' for "
|
|
||||||
"translation, then restores 'Name\\nbody' on inject (byte-safe either way).\n"
|
|
||||||
"</background>\n"
|
|
||||||
"\n"
|
|
||||||
"--- attach the extracted JSON in files/ here (maps / CommonEvent) before continuing ---\n"
|
|
||||||
"\n"
|
|
||||||
"<task>\n"
|
|
||||||
"Decide whether the LOW-confidence first-line guesses (speaker_src = literal_line1_lowconf) "
|
|
||||||
"should be treated as speaker names for THIS game. Inspect a good sample of those entries in "
|
|
||||||
"files/: look at the first line of each such 'source' and judge whether it is genuinely a "
|
|
||||||
"character/speaker label as opposed to real dialogue or narration.\n"
|
|
||||||
" - ENABLE if the low-confidence first lines are overwhelmingly real speaker names.\n"
|
|
||||||
" - DISABLE if many are actually dialogue/narration (reshaping them would mislabel lines).\n"
|
|
||||||
"(The high-confidence nameplates are always handled; you are only ruling on the guesses.)\n"
|
|
||||||
"</task>\n"
|
|
||||||
"\n"
|
|
||||||
"<output_format>\n"
|
|
||||||
"Return ONLY a fenced code block containing the recommendation and a one-line reason, e.g.\n"
|
|
||||||
"```\n"
|
|
||||||
"LOWCONF_FIRSTLINE: ENABLE - low-confidence first lines are short character names (市民, 兵士...).\n"
|
|
||||||
"```\n"
|
|
||||||
"Use exactly ENABLE or DISABLE after the colon.\n"
|
|
||||||
"</output_format>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _WolfTaskWorker(QThread):
|
class _WolfTaskWorker(QThread):
|
||||||
|
|
@ -2160,7 +2120,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
|
|
||||||
def _copy_wolf_speaker_prompt(self):
|
def _copy_wolf_speaker_prompt(self):
|
||||||
try:
|
try:
|
||||||
QApplication.clipboard().setText(_WOLF_SPEAKER_PROMPT)
|
QApplication.clipboard().setText(load_clipboard_skill("wolf_speakers.md"))
|
||||||
self._log(
|
self._log(
|
||||||
"📋 Speaker-format prompt copied. Paste it into Cursor/Copilot with files/ open; "
|
"📋 Speaker-format prompt copied. Paste it into Cursor/Copilot with files/ open; "
|
||||||
"set the low-confidence box to match its recommendation."
|
"set the low-confidence box to match its recommendation."
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from util.paths import VOCAB_PATH, APP_NAME, ORG_NAME
|
from util.paths import VOCAB_PATH, APP_NAME, ORG_NAME
|
||||||
from util.skills import load_project_setup
|
from util.skills import load_clipboard_skill, load_project_setup
|
||||||
from util.vocab import BASE_SEPARATOR as _SHARED_BASE_SEPARATOR
|
from util.vocab import BASE_SEPARATOR as _SHARED_BASE_SEPARATOR
|
||||||
|
|
||||||
import jsbeautifier
|
import jsbeautifier
|
||||||
|
|
@ -318,6 +318,17 @@ _GAMEUPDATE_COPY_SKIP_NAMES = frozenset({
|
||||||
"previous_patch_sha.txt",
|
"previous_patch_sha.txt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# UberWolf is only useful for WOLF RPG Editor archives. The generic workflow
|
||||||
|
# handles RPG Maker MV/MZ/Ace and must never install these files; the dedicated
|
||||||
|
# WOLF workflow intentionally continues using the smaller shared skip set.
|
||||||
|
_WOLF_ONLY_GAMEUPDATE_NAMES = frozenset({
|
||||||
|
"UberWolfCli.exe",
|
||||||
|
"UberWolfCli.LICENSE.txt",
|
||||||
|
})
|
||||||
|
_RPG_GAMEUPDATE_COPY_SKIP_NAMES = (
|
||||||
|
_GAMEUPDATE_COPY_SKIP_NAMES | _WOLF_ONLY_GAMEUPDATE_NAMES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _FileCopyWorker(QThread):
|
class _FileCopyWorker(QThread):
|
||||||
"""Recursively copy a source folder into a destination folder."""
|
"""Recursively copy a source folder into a destination folder."""
|
||||||
|
|
@ -1155,345 +1166,7 @@ class WorkflowTab(QWidget):
|
||||||
|
|
||||||
# ── Step 3: Vocab / Glossary ────────────────────────────────────────────
|
# ── Step 3: Vocab / Glossary ────────────────────────────────────────────
|
||||||
|
|
||||||
# ── Copilot prompt templates ────────────────────────────────────────────
|
# Static clipboard prompts are loaded from editable data/skills/*.md files.
|
||||||
|
|
||||||
_WRAP_PROMPT = (
|
|
||||||
"You are an expert RPGMaker MV/MZ configuration analyst.\n"
|
|
||||||
"\n"
|
|
||||||
"<task>\n"
|
|
||||||
"Calculate the correct text-wrap width settings for a Japanese-to-English RPGMaker MV/MZ "
|
|
||||||
"translation tool. The tool wraps translated English using character-count limits (not pixels). "
|
|
||||||
"I need three values: width, listWidth, and noteWidth.\n"
|
|
||||||
"</task>\n"
|
|
||||||
"\n"
|
|
||||||
"--- attach System.json and js/plugins.js here before continuing ---\n"
|
|
||||||
"\n"
|
|
||||||
"<instructions>\n"
|
|
||||||
"1. Read screenWidth and fontSize from System.json.\n"
|
|
||||||
" Check js/plugins.js for any MessageCore or Window plugin that overrides these values.\n"
|
|
||||||
"2. For each window type, estimate its pixel width, subtract ~48px padding, then calculate:\n"
|
|
||||||
" chars = floor(content_px / (font_size × 0.58))\n"
|
|
||||||
" - width: main dialogue/message box (Show Text) — typically full screen width\n"
|
|
||||||
" - listWidth: item/skill/help description windows — typically full or half screen width\n"
|
|
||||||
" - noteWidth: database note fields — typically the narrowest pane (~40–50% screen width)\n"
|
|
||||||
"3. If font size is above 26px and reducing it would meaningfully increase characters per line, "
|
|
||||||
"note where to change it (System.json or the relevant plugin parameter).\n"
|
|
||||||
"</instructions>\n"
|
|
||||||
"\n"
|
|
||||||
"<output_format>\n"
|
|
||||||
"Output only the final values — do not show calculations:\n"
|
|
||||||
"\n"
|
|
||||||
"```\n"
|
|
||||||
"width=<N>\n"
|
|
||||||
"listWidth=<N>\n"
|
|
||||||
"noteWidth=<N>\n"
|
|
||||||
"fontSize=<N> # or: no change needed\n"
|
|
||||||
"```\n"
|
|
||||||
"\n"
|
|
||||||
"Followed by one sentence of assumptions if anything was estimated.\n"
|
|
||||||
"</output_format>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
_PLUGINS_JS_TRANSLATE_PROMPT = (
|
|
||||||
"You are an expert RPGMaker MV/MZ localisation engineer.\n"
|
|
||||||
"\n"
|
|
||||||
"<task>\n"
|
|
||||||
"Translate visible Japanese strings inside js/plugins.js without breaking any game logic "
|
|
||||||
"or plugin functionality. A vocab.txt glossary is attached — use it as your primary reference. "
|
|
||||||
"Any name or term that appears in the glossary must be translated exactly as shown there.\n"
|
|
||||||
"</task>\n"
|
|
||||||
"\n"
|
|
||||||
"--- attach js/plugins.js and vocab.txt here before continuing ---\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"<translate>\n"
|
|
||||||
"Only translate string values directly shown to the player at runtime:\n"
|
|
||||||
"- Menu/button labels in-game UI (e.g. 決定, キャンセル)\n"
|
|
||||||
"- Scene/window title text (e.g. アイテム, スキル, 装備)\n"
|
|
||||||
"- In-game popup, tooltip, or notification messages\n"
|
|
||||||
"- Default NPC names or pronouns used in UI display\n"
|
|
||||||
"- Help or description text shown in help windows\n"
|
|
||||||
"- Battle log messages or status effect messages\n"
|
|
||||||
"</translate>\n"
|
|
||||||
"\n"
|
|
||||||
"<do_not_translate>\n"
|
|
||||||
"Translating the following WILL BREAK THE GAME:\n"
|
|
||||||
"\n"
|
|
||||||
"1. Plugin parameter keys (object property names).\n"
|
|
||||||
" { \"CommandName\": \"セーブ\" } → translate セーブ but NOT CommandName\n"
|
|
||||||
"\n"
|
|
||||||
"2. Strings used as internal identifiers or lookup keys:\n"
|
|
||||||
" - Switch/variable names matching System.json entries\n"
|
|
||||||
" - Actor, skill, item, weapon, armour names used as keys inside other plugin parameters\n"
|
|
||||||
" - Strings passed as plugin command arguments that the engine looks up\n"
|
|
||||||
"\n"
|
|
||||||
"3. File paths, filenames, URLs, colour codes, font names, icon indices.\n"
|
|
||||||
" (e.g. img/faces/Actor1 or #ffffff)\n"
|
|
||||||
"\n"
|
|
||||||
"4. Plugin names and script identifiers:\n"
|
|
||||||
" - The \"name\" property at the top of each plugin block (e.g. { \"name\": \"YEP_CoreEngine\" })\n"
|
|
||||||
" - Function identifiers, class names, JS event names\n"
|
|
||||||
"\n"
|
|
||||||
"5. Any string in the game data files (Actors.json, Skills.json, Items.json, etc.) that is "
|
|
||||||
"also used as a lookup key in the plugin — changing it here would desync it from the data file.\n"
|
|
||||||
"\n"
|
|
||||||
"6. Boolean strings, numeric strings, regex patterns.\n"
|
|
||||||
"</do_not_translate>\n"
|
|
||||||
"\n"
|
|
||||||
"<safety_check>\n"
|
|
||||||
"Before translating any string, confirm all three are true:\n"
|
|
||||||
"- It is displayed directly to the player as visible text\n"
|
|
||||||
"- It is purely a UI label, not used as a key anywhere else\n"
|
|
||||||
"- Nothing in the codebase compares this exact string to another value\n"
|
|
||||||
"When in doubt, skip it — untranslated Japanese is better than a broken game.\n"
|
|
||||||
"</safety_check>\n"
|
|
||||||
"\n"
|
|
||||||
"<output_format>\n"
|
|
||||||
"Provide the translated file as a complete replacement of js/plugins.js, inside a single "
|
|
||||||
"fenced code block (```js ... ```) so it can be copied in one click. Only change the "
|
|
||||||
"string values identified above. Preserve all original formatting, indentation, comments, and structure.\n"
|
|
||||||
"\n"
|
|
||||||
"After the code block, output a summary:\n"
|
|
||||||
"\n"
|
|
||||||
"### Translations Made\n"
|
|
||||||
" Plugin: <plugin name>\n"
|
|
||||||
" Parameter: <parameter key>\n"
|
|
||||||
" Before: <original Japanese>\n"
|
|
||||||
" After: <English translation>\n"
|
|
||||||
"\n"
|
|
||||||
"### Skipped (Ambiguous or Internal)\n"
|
|
||||||
"List any Japanese strings you detected but did not translate, with a one-line reason.\n"
|
|
||||||
"</output_format>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
_ACE_SCRIPTS_TRANSLATE_PROMPT = (
|
|
||||||
"You are an expert RPGMaker VX Ace (Ruby) localisation engineer.\n"
|
|
||||||
"\n"
|
|
||||||
"<task>\n"
|
|
||||||
"Translate visible Japanese strings inside the game's Ruby scripts (ace_json/scripts/*.rb) "
|
|
||||||
"without breaking any game logic or script functionality. A vocab.txt glossary is attached - "
|
|
||||||
"use it as your primary reference. Any name or term in the glossary must be translated exactly as shown.\n"
|
|
||||||
"</task>\n"
|
|
||||||
"\n"
|
|
||||||
"--- attach the .rb script files and vocab.txt here before continuing ---\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"========================================\n"
|
|
||||||
"## WHAT TO TRANSLATE\n"
|
|
||||||
"========================================\n"
|
|
||||||
"\n"
|
|
||||||
"Only translate string literals that are directly shown to the player at runtime.\n"
|
|
||||||
"These typically appear as:\n"
|
|
||||||
" - Strings passed to msgbox, msgbox_p, print, p\n"
|
|
||||||
" - Labels and text in Window or Scene classes rendered to screen\n"
|
|
||||||
" - draw_text / draw_item calls with a Japanese string literal\n"
|
|
||||||
" - Default UI label text (menu names, button labels, status window text)\n"
|
|
||||||
" - Battle log messages, notifications, popup strings\n"
|
|
||||||
" - Help or description text shown in help windows\n"
|
|
||||||
"\n"
|
|
||||||
"========================================\n"
|
|
||||||
"## WHAT MUST NOT BE TRANSLATED\n"
|
|
||||||
"========================================\n"
|
|
||||||
"\n"
|
|
||||||
"CRITICAL - translating the following will break the game:\n"
|
|
||||||
"\n"
|
|
||||||
" 1. Strings used as hash keys, method names, or symbol equivalents.\n"
|
|
||||||
" Example: vocab[\"HP\"] = \"体力\" -> translate \"体力\" but NOT the key \"HP\"\n"
|
|
||||||
"\n"
|
|
||||||
" 2. Strings used as internal identifiers compared with == or used in case/when:\n"
|
|
||||||
" - Actor/class/skill/item names used as lookup strings\n"
|
|
||||||
" - Script-internal state names or flag strings\n"
|
|
||||||
" Example: if type == \"スキル\" -> do NOT translate \"スキル\"\n"
|
|
||||||
"\n"
|
|
||||||
" 3. File paths, filenames, font names, colour strings, URLs.\n"
|
|
||||||
"\n"
|
|
||||||
" 4. Regular expressions, format strings used with sprintf or % operator\n"
|
|
||||||
" where the placeholders must stay in the same position.\n"
|
|
||||||
" (You may translate the human-readable parts but keep %s / %d / %1 etc intact.)\n"
|
|
||||||
"\n"
|
|
||||||
" 5. Script class names, module names, method names, constants.\n"
|
|
||||||
"\n"
|
|
||||||
" 6. Any string that is read back elsewhere in the scripts with an exact match.\n"
|
|
||||||
"\n"
|
|
||||||
"<safety_check>\n"
|
|
||||||
"Before translating any string, confirm all three are true:\n"
|
|
||||||
"- It is displayed directly to the player as visible text\n"
|
|
||||||
"- It is purely a display string, not compared or looked up anywhere\n"
|
|
||||||
"- Changing it would break no conditional logic or data lookup\n"
|
|
||||||
"When in doubt, skip it - untranslated Japanese is better than a broken game.\n"
|
|
||||||
"</safety_check>\n"
|
|
||||||
"\n"
|
|
||||||
"<output_format>\n"
|
|
||||||
"For each .rb file that needed changes, provide the full translated file content inside its "
|
|
||||||
"own fenced code block (```ruby ... ```), preceded by the filename, so each file can be "
|
|
||||||
"copied in one click. Only change string values identified as safe. "
|
|
||||||
"Preserve all Ruby syntax, indentation, comments, and structure exactly.\n"
|
|
||||||
"\n"
|
|
||||||
"After all files, output a summary:\n"
|
|
||||||
"\n"
|
|
||||||
"### Translations Made\n"
|
|
||||||
" File: <script filename.rb>\n"
|
|
||||||
" Before: <original Japanese>\n"
|
|
||||||
" After: <English translation>\n"
|
|
||||||
"\n"
|
|
||||||
"### Skipped (Ambiguous or Internal)\n"
|
|
||||||
"List any Japanese strings you detected but did not translate, with a one-line reason.\n"
|
|
||||||
"</output_format>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
_PLUGIN_PROMPT = (
|
|
||||||
"You are an expert RPGMaker MV/MZ event system analyst.\n"
|
|
||||||
"\n"
|
|
||||||
"<task>\n"
|
|
||||||
"Audit several optional event code types and report: (a) which contain player-visible "
|
|
||||||
"Japanese text that needs translation, and (b) for code 122, exactly which variable ID "
|
|
||||||
"ranges carry display text versus internal keys.\n"
|
|
||||||
"</task>\n"
|
|
||||||
"\n"
|
|
||||||
"--- attach Actors.json, CommonEvents.json, Troops.json, Map*.json, and js/plugins.js here ---\n"
|
|
||||||
"\n"
|
|
||||||
"<actor_context>\n"
|
|
||||||
"Read Actors.json in full before auditing event commands. Use it to resolve actor IDs, "
|
|
||||||
"\\N[n] references, and code 320/324/325 targets. If you see an event reference to a "
|
|
||||||
"major actor whose full character vocab is missing or only appears as a placeholder "
|
|
||||||
"mapping like \\N[3] (Keimi), report that Actors.json should be included in the glossary "
|
|
||||||
"build and that a full # Game Characters entry is needed.\n"
|
|
||||||
"</actor_context>\n"
|
|
||||||
"\n"
|
|
||||||
"<audit_1 code=\"122\">\n"
|
|
||||||
"Code 122 — Control Variables — which variable IDs carry display text\n"
|
|
||||||
"\n"
|
|
||||||
"A code 122 entry looks like:\n"
|
|
||||||
" { \"code\": 122, \"parameters\": [startVarId, endVarId, 0, 4, \"\\\"some string\\\"\"] }\n"
|
|
||||||
"parameters[0] is the variable ID being set; parameters[4] is the string value\n"
|
|
||||||
"(only present when parameters[3] == 4, direct string assignment).\n"
|
|
||||||
"\n"
|
|
||||||
"Collect every code 122 where parameters[3] == 4. For each variable ID found:\n"
|
|
||||||
" 1. Is the string also tested in a code 111 $gameVariables comparison?\n"
|
|
||||||
" → DO NOT TRANSLATE (would break game logic)\n"
|
|
||||||
" 2. Is the string used as an internal ID / plugin key / script argument?\n"
|
|
||||||
" → DO NOT TRANSLATE\n"
|
|
||||||
" 3. Is the string purely player-visible display text?\n"
|
|
||||||
" → SAFE TO TRANSLATE\n"
|
|
||||||
"\n"
|
|
||||||
"Summarise translatable variable IDs as compact numeric ranges,\n"
|
|
||||||
"e.g. 'Translate IDs: 5, 10-18, 42'. Separately list any DO NOT TRANSLATE IDs\n"
|
|
||||||
"with the reason. The ranges will be entered as min/max in the translation tool.\n"
|
|
||||||
"</audit_1>\n"
|
|
||||||
"\n"
|
|
||||||
"<audit_2>\n"
|
|
||||||
"Plugin codes with visible text — 357 / 356 / 355-655 / 657 / 320 / 324 / 325 / 108\n"
|
|
||||||
"For each code type below, scan all events and report whether any instance contains "
|
|
||||||
"Japanese text visible to the player at runtime.\n"
|
|
||||||
"\n"
|
|
||||||
"--- Code 357 (MZ Plugin Commands) ---\n"
|
|
||||||
"parameters[0] = plugin header; parameters[3] = data dict.\n"
|
|
||||||
"Look up each unique header in js/plugins.js.\n"
|
|
||||||
"Does any key in parameters[3] hold Japanese text shown on screen?\n"
|
|
||||||
"The module already handles: AdvExtentionllk, VisuMZ_4_ProximityMessages, LL_GalgeChoiceWindow\n"
|
|
||||||
"For other headers with visible text, check the commented-out headerMappings list:\n"
|
|
||||||
" \"LL_InfoPopupWIndow\": ([\"messageText\"], None),\n"
|
|
||||||
" \"QuestSystem\": ([\"DetailNote\"], None),\n"
|
|
||||||
" \"BalloonInBattle\": ([\"text\"], None),\n"
|
|
||||||
" \"MNKR_CommonPopupCoreMZ\": ([\"text\"], None),\n"
|
|
||||||
" \"DestinationWindow\": ([\"destination\"], None),\n"
|
|
||||||
" \"_TMLogWindowMZ\": ([\"text\"], None),\n"
|
|
||||||
" \"TorigoyaMZ_NotifyMessage\": ([\"message\"], None),\n"
|
|
||||||
" \"SoR_GabWindow\": ([\"arg1\"], None),\n"
|
|
||||||
" \"DarkPlasma_CharacterText\": ([\"text\"], None),\n"
|
|
||||||
" \"DTextPicture\": ([\"text\"], None),\n"
|
|
||||||
" \"TextPicture\": ([\"text\"], None),\n"
|
|
||||||
" \"TRP_SkitMZ\": ([\"name\"], None),\n"
|
|
||||||
" \"LogWindow\": ([\"text\"], None),\n"
|
|
||||||
" \"BattleLogOutput\": ([\"message\"], None),\n"
|
|
||||||
" \"TorigoyaMZ_NotifyMessage_CommandMessage\": ([\"message\"], None),\n"
|
|
||||||
" \"NUUN_SaveScreen\": ([\"AnyName\"], None),\n"
|
|
||||||
" \"build/ARPG_Core\": ([\"Text\", \"SkillByName\"], None),\n"
|
|
||||||
"Output: EXACT uncomment line, or describe parameters[3] if header is new.\n"
|
|
||||||
"\n"
|
|
||||||
"--- Code 356 (MV Plugin Commands) ---\n"
|
|
||||||
"parameters[0] is a space-delimited string, e.g. 'D_TEXT テキスト 24'.\n"
|
|
||||||
"The module already handles: D_TEXT, ShowInfo, PushGab, addLog, DW_*, CommonPopup, AddCustomChoice.\n"
|
|
||||||
"Does any 356 line have Japanese that is shown on screen?\n"
|
|
||||||
"If yes: ENABLE CODE356 and list the command keywords found.\n"
|
|
||||||
"If no: SKIP CODE356.\n"
|
|
||||||
"\n"
|
|
||||||
"--- Code 355/655 (Inline Scripts) ---\n"
|
|
||||||
"Code 355 starts a script block; code 655 continues it.\n"
|
|
||||||
"parameters[0] is the raw JS/script text.\n"
|
|
||||||
"For each block with Japanese in a string passed to a message/popup/log function:\n"
|
|
||||||
" • The leading keyword/pattern that identifies the block\n"
|
|
||||||
" • A regex capturing only the display substring, e.g. テキスト-(.+)\n"
|
|
||||||
" • Whether it is single-line (355 only) or multi-line (355 + 655)\n"
|
|
||||||
" • The exact entry to add to the patterns dict:\n"
|
|
||||||
" \"<keyword>\": (r\"<regex>\", <True|False>),\n"
|
|
||||||
"\n"
|
|
||||||
"--- Code 657 (Picture Text) ---\n"
|
|
||||||
"parameters[0] is a plain string drawn onto a picture.\n"
|
|
||||||
"Does any 657 entry contain Japanese text visible on screen (not a filename)?\n"
|
|
||||||
"If yes: ENABLE CODE657. If no: SKIP CODE657.\n"
|
|
||||||
"\n"
|
|
||||||
"--- Codes 320 / 324 / 325 (Actor Name / Nickname / Profile) ---\n"
|
|
||||||
"parameters[1] is the new name/nickname/profile string.\n"
|
|
||||||
"Do any of these codes set Japanese strings visible to the player\n"
|
|
||||||
"(not just internal IDs or filenames)?\n"
|
|
||||||
"If yes: ENABLE the respective code. If no: SKIP.\n"
|
|
||||||
"\n"
|
|
||||||
"--- Code 108 (Comment / Notetag) ---\n"
|
|
||||||
"parameters[0] is a comment string used as a plugin notetag.\n"
|
|
||||||
"The module only translates 108 lines that match specific patterns:\n"
|
|
||||||
" info:, ActiveMessage:, event_text, Menu Name, text_indicator, NW名前指定\n"
|
|
||||||
"Do any 108 entries matching those patterns have Japanese visible to the player?\n"
|
|
||||||
"If yes: ENABLE CODE108 and list the patterns found. If no: SKIP CODE108.\n"
|
|
||||||
"</audit_2>\n"
|
|
||||||
"\n"
|
|
||||||
"<output_format>\n"
|
|
||||||
"Reply with these exact sections:\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 122 — Variable Translation Ranges\n"
|
|
||||||
" Translate IDs : <compact ranges, e.g. 5, 10-18, 42>\n"
|
|
||||||
" Do NOT translate: <ID — reason>\n"
|
|
||||||
"If none: 'No display-text string assignments found.'\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 357 — Plugin Handlers to Enable\n"
|
|
||||||
" • Already handled : <header> — no action needed\n"
|
|
||||||
" • Enable in module : uncomment → \"<header>\": ([\"<key>\"], None),\n"
|
|
||||||
" • New entry needed : <header> — parameters[3] structure: <description>\n"
|
|
||||||
" • No visible text : <header> — internal only, skip\n"
|
|
||||||
"If none: 'No code 357 visible text found.'\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 356 — Enable or Skip\n"
|
|
||||||
" ENABLE / SKIP — keywords found: <list>\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 355/655 — Script Patterns to Add\n"
|
|
||||||
" • Pattern key : <keyword>\n"
|
|
||||||
" • Regex : <capture regex>\n"
|
|
||||||
" • Multiline : <true/false>\n"
|
|
||||||
" • Module line : \"<keyword>\": (r\"<regex>\", <True|False>),\n"
|
|
||||||
"If none: 'No translatable 355/655 scripts found.'\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 657 — Enable or Skip\n"
|
|
||||||
" ENABLE / SKIP — <brief reason>\n"
|
|
||||||
"\n"
|
|
||||||
"### Codes 320 / 324 / 325 — Enable or Skip\n"
|
|
||||||
" 320: ENABLE / SKIP 324: ENABLE / SKIP 325: ENABLE / SKIP\n"
|
|
||||||
"\n"
|
|
||||||
"### Code 108 — Enable or Skip\n"
|
|
||||||
" ENABLE / SKIP — patterns found: <list>\n"
|
|
||||||
"\n"
|
|
||||||
"### GUI Action Summary\n"
|
|
||||||
"\n"
|
|
||||||
"After all audit sections above, output this final block to configure the GUI:\n"
|
|
||||||
"\n"
|
|
||||||
"ENABLE CODES : <comma-separated CODE* keys to check, e.g. CODE357, CODE356>\n"
|
|
||||||
"SKIP CODES : <codes that have no visible text>\n"
|
|
||||||
"357 PLUGINS : <comma-separated HEADER_MAPPINGS_357 keys to enable>\n"
|
|
||||||
"355/655 PATTERNS : <comma-separated PATTERNS_355655 keys to enable>\n"
|
|
||||||
"122 VAR RANGE : min=<N>, max=<M>\n"
|
|
||||||
"\n"
|
|
||||||
"If a field has nothing to fill in, write NONE.\n"
|
|
||||||
"</output_format>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── Step 1 (Optional): Pre-process ────────────────────────────────
|
# ── Step 1 (Optional): Pre-process ────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -1622,6 +1295,7 @@ class WorkflowTab(QWidget):
|
||||||
tc_desc = QLabel(
|
tc_desc = QLabel(
|
||||||
"Copies everything from the <code>gameupdate/</code> folder "
|
"Copies everything from the <code>gameupdate/</code> folder "
|
||||||
"into the game\'s root folder, overwriting existing files. "
|
"into the game\'s root folder, overwriting existing files. "
|
||||||
|
"WOLF-only UberWolf files are omitted for RPG Maker games. "
|
||||||
"Writes <code>patch-config.txt</code> from Config → Game Update defaults "
|
"Writes <code>patch-config.txt</code> from Config → Game Update defaults "
|
||||||
"(set your org once there; edit <code>repo=</code> per game)."
|
"(set your org once there; edit <code>repo=</code> per game)."
|
||||||
)
|
)
|
||||||
|
|
@ -2411,8 +2085,8 @@ class WorkflowTab(QWidget):
|
||||||
|
|
||||||
self._step6_copy_btn = _make_btn("📋 Copy Prompt for Copilot", "#555")
|
self._step6_copy_btn = _make_btn("📋 Copy Prompt for Copilot", "#555")
|
||||||
self._step6_copy_btn.setToolTip(
|
self._step6_copy_btn.setToolTip(
|
||||||
"Copy a prompt that instructs Copilot/Cursor to translate only "
|
"Copy a prompt that audits plugins.js and enabled plugin sources, asks what "
|
||||||
"visible player-facing strings in plugins.js, using vocab.txt as a glossary."
|
"needs translation, then edits approved player-visible strings in place."
|
||||||
)
|
)
|
||||||
self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt)
|
self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt)
|
||||||
|
|
||||||
|
|
@ -3129,13 +2803,13 @@ class WorkflowTab(QWidget):
|
||||||
if is_ace:
|
if is_ace:
|
||||||
btn.setToolTip(
|
btn.setToolTip(
|
||||||
"Copy a prompt that instructs Copilot/Cursor to translate only "
|
"Copy a prompt that instructs Copilot/Cursor to translate only "
|
||||||
"visible player-facing strings in the Ace .rb script files "
|
"visible player-facing strings in the Ace .rb script files. "
|
||||||
"(ace_json/scripts/*.rb)."
|
"It audits first, asks what to translate, then edits approved files in place."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
btn.setToolTip(
|
btn.setToolTip(
|
||||||
"Copy a prompt that instructs Copilot/Cursor to translate only "
|
"Copy a prompt that audits plugins.js and enabled plugin sources, asks what "
|
||||||
"visible player-facing strings in plugins.js, using vocab.txt as a glossary."
|
"needs translation, then edits approved player-visible strings in place."
|
||||||
)
|
)
|
||||||
# Step 6 - TL Inspector (MV/MZ only; hidden for Ace)
|
# Step 6 - TL Inspector (MV/MZ only; hidden for Ace)
|
||||||
show_playtest = not is_ace
|
show_playtest = not is_ace
|
||||||
|
|
@ -3567,8 +3241,10 @@ class WorkflowTab(QWidget):
|
||||||
self._copy_project_setup_prompt()
|
self._copy_project_setup_prompt()
|
||||||
|
|
||||||
def _copy_wrap_prompt(self):
|
def _copy_wrap_prompt(self):
|
||||||
QApplication.clipboard().setText(self._WRAP_PROMPT)
|
self._copy_clipboard_skill(
|
||||||
self._log("Text-wrap analysis prompt copied to clipboard.")
|
"wrap_config.md",
|
||||||
|
"Text-wrap analysis prompt copied to clipboard.",
|
||||||
|
)
|
||||||
|
|
||||||
def _apply_var_range(self):
|
def _apply_var_range(self):
|
||||||
"""Write CODE122_VAR_MIN / CODE122_VAR_MAX to the module file."""
|
"""Write CODE122_VAR_MIN / CODE122_VAR_MAX to the module file."""
|
||||||
|
|
@ -3736,15 +3412,29 @@ class WorkflowTab(QWidget):
|
||||||
getattr(self, "_ace_rvdata_dir", "") or getattr(self, "_ace_json_dir", "")
|
getattr(self, "_ace_rvdata_dir", "") or getattr(self, "_ace_json_dir", "")
|
||||||
)
|
)
|
||||||
if is_ace:
|
if is_ace:
|
||||||
QApplication.clipboard().setText(self._ACE_SCRIPTS_TRANSLATE_PROMPT)
|
self._copy_clipboard_skill(
|
||||||
self._log("Ace scripts translation prompt copied to clipboard.")
|
"ace_script_translation.md",
|
||||||
|
"Ace scripts translation prompt copied to clipboard.",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
QApplication.clipboard().setText(self._PLUGINS_JS_TRANSLATE_PROMPT)
|
self._copy_clipboard_skill(
|
||||||
self._log("plugins.js translation prompt copied to clipboard.")
|
"plugin_translation.md",
|
||||||
|
"plugins.js translation prompt copied to clipboard.",
|
||||||
|
)
|
||||||
|
|
||||||
def _copy_plugin_prompt(self):
|
def _copy_plugin_prompt(self):
|
||||||
QApplication.clipboard().setText(self._PLUGIN_PROMPT)
|
self._copy_clipboard_skill(
|
||||||
self._log("Risky codes analysis prompt copied to clipboard.")
|
"risky_codes.md",
|
||||||
|
"Risky codes analysis prompt copied to clipboard.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _copy_clipboard_skill(self, filename: str, success_message: str):
|
||||||
|
try:
|
||||||
|
prompt = load_clipboard_skill(filename)
|
||||||
|
QApplication.clipboard().setText(prompt)
|
||||||
|
self._log(success_message)
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"❌ Could not copy {filename}: {exc}")
|
||||||
|
|
||||||
def _apply_wrap_config(self):
|
def _apply_wrap_config(self):
|
||||||
"""Write width / listWidth / noteWidth back into .env."""
|
"""Write width / listWidth / noteWidth back into .env."""
|
||||||
|
|
@ -4414,7 +4104,7 @@ class WorkflowTab(QWidget):
|
||||||
if not Path(src).is_dir():
|
if not Path(src).is_dir():
|
||||||
self._log(f"⚠ gameupdate folder not found: {src}")
|
self._log(f"⚠ gameupdate folder not found: {src}")
|
||||||
return
|
return
|
||||||
w = _FileCopyWorker(src, dst, skip_names=_GAMEUPDATE_COPY_SKIP_NAMES)
|
w = _FileCopyWorker(src, dst, skip_names=_RPG_GAMEUPDATE_COPY_SKIP_NAMES)
|
||||||
w.log.connect(self._log)
|
w.log.connect(self._log)
|
||||||
w.done.connect(self._on_gameupdate_done)
|
w.done.connect(self._on_gameupdate_done)
|
||||||
self._worker = w
|
self._worker = w
|
||||||
|
|
@ -4454,7 +4144,7 @@ class WorkflowTab(QWidget):
|
||||||
_FileCopyWorker(
|
_FileCopyWorker(
|
||||||
gameupdate_src,
|
gameupdate_src,
|
||||||
game_root_dst,
|
game_root_dst,
|
||||||
skip_names=_GAMEUPDATE_COPY_SKIP_NAMES,
|
skip_names=_RPG_GAMEUPDATE_COPY_SKIP_NAMES,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -163,14 +163,14 @@ CODE122_VAR_MAX = 2000
|
||||||
|
|
||||||
# Plugins / Scripts
|
# Plugins / Scripts
|
||||||
CODE355655 = False
|
CODE355655 = False
|
||||||
CODE357 = False
|
CODE357 = True
|
||||||
CODE657 = False
|
CODE657 = False
|
||||||
CODE356 = False
|
CODE356 = False
|
||||||
CODE320 = False
|
CODE320 = False
|
||||||
CODE324 = False
|
CODE324 = False
|
||||||
CODE325 = False
|
CODE325 = False
|
||||||
CODE111 = True
|
CODE111 = False
|
||||||
CODE108 = False
|
CODE108 = True
|
||||||
|
|
||||||
# ─── Plugin Manager ──────────────────────────────────────────────────────────
|
# ─── Plugin Manager ──────────────────────────────────────────────────────────
|
||||||
# All known code-357 headerMapping entries. Enable entries via ENABLED_PLUGINS_357.
|
# All known code-357 headerMapping entries. Enable entries via ENABLED_PLUGINS_357.
|
||||||
|
|
@ -201,7 +201,7 @@ HEADER_MAPPINGS_357 = {
|
||||||
"SceneGlossary": (["category"], None),
|
"SceneGlossary": (["category"], None),
|
||||||
}
|
}
|
||||||
# Subset of HEADER_MAPPINGS_357 keys that should be processed (empty = none).
|
# Subset of HEADER_MAPPINGS_357 keys that should be processed (empty = none).
|
||||||
ENABLED_PLUGINS_357: set = set()
|
ENABLED_PLUGINS_357: set = {"DTextPicture"}
|
||||||
|
|
||||||
# All known code-355/655 script patterns. Enable entries via ENABLED_PATTERNS_355655.
|
# All known code-355/655 script patterns. Enable entries via ENABLED_PATTERNS_355655.
|
||||||
PATTERNS_355655 = {
|
PATTERNS_355655 = {
|
||||||
|
|
@ -236,7 +236,7 @@ PATTERNS_355655 = {
|
||||||
"AddAddress": (r'AddAddress\(\d+,\s*\\?"(.+?)\\?"', False),
|
"AddAddress": (r'AddAddress\(\d+,\s*\\?"(.+?)\\?"', False),
|
||||||
}
|
}
|
||||||
# Subset of PATTERNS_355655 keys that should be processed (empty = none).
|
# Subset of PATTERNS_355655 keys that should be processed (empty = none).
|
||||||
ENABLED_PATTERNS_355655: set = {"BattleManager._logWindow.addText"}
|
ENABLED_PATTERNS_355655: set = set()
|
||||||
|
|
||||||
|
|
||||||
def _pat355655_captured_text(match):
|
def _pat355655_captured_text(match):
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ _SHIPPED_DATA_FILES = (
|
||||||
"data/translation_contexts.json",
|
"data/translation_contexts.json",
|
||||||
"data/skills/system.md",
|
"data/skills/system.md",
|
||||||
"data/skills/project_setup.md",
|
"data/skills/project_setup.md",
|
||||||
|
"data/skills/wrap_config.md",
|
||||||
|
"data/skills/plugin_translation.md",
|
||||||
|
"data/skills/ace_script_translation.md",
|
||||||
|
"data/skills/risky_codes.md",
|
||||||
|
"data/skills/wolf_speakers.md",
|
||||||
"data/help/index.json",
|
"data/help/index.json",
|
||||||
"data/help/00-welcome.md",
|
"data/help/00-welcome.md",
|
||||||
"data/vocab_base.txt",
|
"data/vocab_base.txt",
|
||||||
|
|
|
||||||
76
tests/test_gameupdate_engine_filter.py
Normal file
76
tests/test_gameupdate_engine_filter.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""Regression tests for keeping UberWolf tools exclusive to WOLF games."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from gui.workflow_tab import (
|
||||||
|
_FileCopyWorker,
|
||||||
|
_GAMEUPDATE_COPY_SKIP_NAMES,
|
||||||
|
_RPG_GAMEUPDATE_COPY_SKIP_NAMES,
|
||||||
|
_WOLF_ONLY_GAMEUPDATE_NAMES,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
class GameUpdateGuiCopyTests(unittest.TestCase):
|
||||||
|
def _copy_with(self, skip_names: frozenset[str]) -> tuple[Path, tempfile.TemporaryDirectory]:
|
||||||
|
tmp = tempfile.TemporaryDirectory()
|
||||||
|
base = Path(tmp.name)
|
||||||
|
src = base / "source"
|
||||||
|
dst = base / "game"
|
||||||
|
src.mkdir()
|
||||||
|
(src / "GameUpdate.bat").write_text("launcher", encoding="utf-8")
|
||||||
|
(src / "UberWolfCli.exe").write_bytes(b"wolf-cli")
|
||||||
|
(src / "UberWolfCli.LICENSE.txt").write_text("license", encoding="utf-8")
|
||||||
|
|
||||||
|
result = []
|
||||||
|
worker = _FileCopyWorker(str(src), str(dst), skip_names=skip_names)
|
||||||
|
worker.done.connect(lambda count, errors: result.append((count, errors)))
|
||||||
|
worker.run()
|
||||||
|
|
||||||
|
self.assertEqual(result, [(1 if _WOLF_ONLY_GAMEUPDATE_NAMES <= skip_names else 3, [])])
|
||||||
|
return dst, tmp
|
||||||
|
|
||||||
|
def test_rpg_copy_omits_uberwolf_files(self):
|
||||||
|
dst, tmp = self._copy_with(_RPG_GAMEUPDATE_COPY_SKIP_NAMES)
|
||||||
|
try:
|
||||||
|
self.assertTrue((dst / "GameUpdate.bat").is_file())
|
||||||
|
for name in _WOLF_ONLY_GAMEUPDATE_NAMES:
|
||||||
|
self.assertFalse((dst / name).exists())
|
||||||
|
finally:
|
||||||
|
tmp.cleanup()
|
||||||
|
|
||||||
|
def test_wolf_copy_keeps_uberwolf_files(self):
|
||||||
|
dst, tmp = self._copy_with(_GAMEUPDATE_COPY_SKIP_NAMES)
|
||||||
|
try:
|
||||||
|
for name in _WOLF_ONLY_GAMEUPDATE_NAMES:
|
||||||
|
self.assertTrue((dst / name).is_file())
|
||||||
|
finally:
|
||||||
|
tmp.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class GameUpdateSelfUpdateTests(unittest.TestCase):
|
||||||
|
def test_powershell_patch_gates_uberwolf_on_wolf_detection(self):
|
||||||
|
text = (ROOT / "gameupdate/gameupdate/patch.ps1").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn("function Test-WolfGameRoot", text)
|
||||||
|
self.assertIn("$isWolfGame = Test-WolfGameRoot -Root $Root", text)
|
||||||
|
self.assertIn("(-not $isWolfGame) -and ($file.Name -in $wolfOnlyNames)", text)
|
||||||
|
|
||||||
|
def test_shell_patch_gates_uberwolf_and_is_valid_bash(self):
|
||||||
|
path = ROOT / "gameupdate/gameupdate/patch.sh"
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn("is_wolf_game()", text)
|
||||||
|
self.assertIn('[ "$wolf_patch" -ne 1 ]', text)
|
||||||
|
subprocess.run(["bash", "-n", str(path)], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
67
tests/test_workflow_prompts.py
Normal file
67
tests/test_workflow_prompts.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
"""Regression tests for concise, interactive plugin translation prompts."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from util.skills import load_clipboard_skill
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTranslationPromptTests(unittest.TestCase):
|
||||||
|
def _assert_interactive_in_place_prompt(self, prompt: str):
|
||||||
|
lowered = prompt.casefold()
|
||||||
|
self.assertIn("do not edit anything yet", lowered)
|
||||||
|
self.assertIn("ask one focused question", lowered)
|
||||||
|
self.assertIn("offer to translate all safe items yourself", lowered)
|
||||||
|
self.assertIn("edit approved", lowered)
|
||||||
|
self.assertIn("never paste or repost an entire", lowered)
|
||||||
|
self.assertIn("minimal unified diff", lowered)
|
||||||
|
self.assertNotIn("complete replacement", lowered)
|
||||||
|
self.assertNotIn("full translated file", lowered)
|
||||||
|
|
||||||
|
def test_mvmz_prompt_audits_enabled_plugins_before_editing(self):
|
||||||
|
prompt = load_clipboard_skill("plugin_translation.md")
|
||||||
|
self._assert_interactive_in_place_prompt(prompt)
|
||||||
|
self.assertIn("js/plugins.js", prompt)
|
||||||
|
self.assertIn("js/plugins/<PluginName>.js", prompt)
|
||||||
|
self.assertIn("which listed plugins should you translate", prompt)
|
||||||
|
|
||||||
|
def test_ace_prompt_audits_scripts_before_editing(self):
|
||||||
|
prompt = load_clipboard_skill("ace_script_translation.md")
|
||||||
|
self._assert_interactive_in_place_prompt(prompt)
|
||||||
|
self.assertIn("ace_json/scripts/*.rb", prompt)
|
||||||
|
self.assertIn("which listed scripts should you translate", prompt)
|
||||||
|
self.assertIn("Ruby interpolation (#{...})", prompt)
|
||||||
|
|
||||||
|
def test_static_clipboard_prompts_live_under_data_skills(self):
|
||||||
|
expected = (
|
||||||
|
"wrap_config.md",
|
||||||
|
"plugin_translation.md",
|
||||||
|
"ace_script_translation.md",
|
||||||
|
"risky_codes.md",
|
||||||
|
"wolf_speakers.md",
|
||||||
|
)
|
||||||
|
for name in expected:
|
||||||
|
with self.subTest(name=name):
|
||||||
|
self.assertTrue(load_clipboard_skill(name).strip())
|
||||||
|
|
||||||
|
workflow_source = (ROOT / "gui/workflow_tab.py").read_text(encoding="utf-8")
|
||||||
|
wolf_source = (ROOT / "gui/wolf_workflow_tab.py").read_text(encoding="utf-8")
|
||||||
|
for constant in (
|
||||||
|
"_WRAP_PROMPT",
|
||||||
|
"_PLUGINS_JS_TRANSLATE_PROMPT",
|
||||||
|
"_ACE_SCRIPTS_TRANSLATE_PROMPT",
|
||||||
|
"_PLUGIN_PROMPT",
|
||||||
|
):
|
||||||
|
self.assertNotIn(constant, workflow_source)
|
||||||
|
self.assertNotIn("_WOLF_SPEAKER_PROMPT", wolf_source)
|
||||||
|
|
||||||
|
def test_clipboard_skill_loader_rejects_paths(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
load_clipboard_skill("../system.md")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from util.skills.contexts import ctx, reload_contexts
|
from util.skills.contexts import ctx, reload_contexts
|
||||||
from util.skills.setup import load_project_setup, skills_dir
|
from util.skills.setup import load_clipboard_skill, load_project_setup, skills_dir
|
||||||
from util.skills.system import (
|
from util.skills.system import (
|
||||||
custom_skill_path_for_game,
|
custom_skill_path_for_game,
|
||||||
game_skill_path_for_game,
|
game_skill_path_for_game,
|
||||||
|
|
@ -19,6 +19,7 @@ __all__ = [
|
||||||
"custom_skill_path_for_game",
|
"custom_skill_path_for_game",
|
||||||
"game_skill_path_for_game",
|
"game_skill_path_for_game",
|
||||||
"list_custom_skill_paths",
|
"list_custom_skill_paths",
|
||||||
|
"load_clipboard_skill",
|
||||||
"load_project_setup",
|
"load_project_setup",
|
||||||
"load_system_prompt",
|
"load_system_prompt",
|
||||||
"migrate_game_skill_text",
|
"migrate_game_skill_text",
|
||||||
|
|
|
||||||
|
|
@ -58,5 +58,18 @@ def load_project_setup(engine: str = "rpgmaker", *, prepend: str = "") -> str:
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def load_clipboard_skill(name: str) -> str:
|
||||||
|
"""Load a static clipboard prompt from ``data/skills``.
|
||||||
|
|
||||||
|
Read on every copy so edits made in the Skills tab take effect immediately.
|
||||||
|
Only a plain Markdown filename is accepted to keep callers inside the shipped
|
||||||
|
skills directory.
|
||||||
|
"""
|
||||||
|
path_name = Path(name)
|
||||||
|
if path_name.name != name or path_name.suffix.casefold() != ".md":
|
||||||
|
raise ValueError(f"Invalid clipboard skill filename: {name!r}")
|
||||||
|
return _read_skill_file(name).strip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
def skills_dir() -> Path:
|
def skills_dir() -> Path:
|
||||||
return SKILLS_DIR
|
return SKILLS_DIR
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue