feat(rpgmaker): add image translation skill functionality
- Introduced a new button to copy a bitmap-localization skill for editable images, allowing users to easily paste it into coding agents. - Implemented logic to handle the copying of project-specific paths, ensuring placeholders are replaced with actual values. - Added a new file page for the image translation skill in the skills tab for better accessibility. - Updated tests to verify the functionality of the new translation skill button and its integration with editable images.
This commit is contained in:
parent
7dee0b7f20
commit
87347d5a43
6 changed files with 373 additions and 1 deletions
266
data/skills/image_translation.md
Normal file
266
data/skills/image_translation.md
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
# Localize Editable Bitmap UI
|
||||||
|
|
||||||
|
<task_context>
|
||||||
|
Translate player-visible source-language text embedded in the editable RPG Maker bitmap UI
|
||||||
|
assets under this folder, recursively:
|
||||||
|
|
||||||
|
`{{EDITABLE_IMAGES_FOLDER}}`
|
||||||
|
|
||||||
|
Use this game project as read-only context for vocabulary, image usages, event data, plugins,
|
||||||
|
scripts, runtime coordinates, scaling, opacity, and dynamic values:
|
||||||
|
|
||||||
|
`{{GAME_ROOT}}`
|
||||||
|
|
||||||
|
Use this file as the authoritative glossary for every translation:
|
||||||
|
|
||||||
|
`{{VOCAB_FILE}}`
|
||||||
|
|
||||||
|
Read `vocab.txt` before translating any image. Reuse its established names, terms, spelling,
|
||||||
|
capitalization, and style consistently. If it is missing or does not cover a term, report that
|
||||||
|
gap and follow the remaining translation precedence below.
|
||||||
|
|
||||||
|
The user approves deterministic edits to the PNGs already present under the editable image
|
||||||
|
folder. Do not modify runtime game images or any other project files; the Image Manager will
|
||||||
|
patch validated edits later. Store backups and temporary candidates outside the editable image
|
||||||
|
folder so they cannot be mistaken for patchable assets. Work through all editable PNGs without
|
||||||
|
asking for confirmation unless a hard safety rule below requires review.
|
||||||
|
</task_context>
|
||||||
|
|
||||||
|
Translate embedded bitmap text by reconstructing the smallest safe UI regions and rendering
|
||||||
|
approved target text. Treat each bitmap as a structured interface asset, not merely an OCR
|
||||||
|
surface. Do not use generative image editing or inpainting.
|
||||||
|
|
||||||
|
## Required outcome
|
||||||
|
|
||||||
|
Produce localized images that:
|
||||||
|
|
||||||
|
- Preserve the original dimensions, format, color mode, and alpha channel.
|
||||||
|
- Change only approved text or panel regions.
|
||||||
|
- Leave portraits and protected artwork pixel-identical when feasible.
|
||||||
|
- Preserve empty spaces used for runtime-drawn values.
|
||||||
|
- Fit translated text without clipping or collisions.
|
||||||
|
- Retain recoverable originals.
|
||||||
|
- Include a concise validation report.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### 1. Establish scope
|
||||||
|
|
||||||
|
Recursively enumerate the PNGs in the editable image folder. Identify:
|
||||||
|
|
||||||
|
- Visible source-language text and intended in-place output paths.
|
||||||
|
- Authoritative glossary or vocabulary files.
|
||||||
|
- Related image variants that share a layout.
|
||||||
|
- Images without player-visible text, which must remain untouched.
|
||||||
|
|
||||||
|
The task context grants editing authority only for existing PNGs inside the editable image
|
||||||
|
folder.
|
||||||
|
|
||||||
|
### 2. Preserve originals
|
||||||
|
|
||||||
|
Create or verify a backup before changing any material asset.
|
||||||
|
|
||||||
|
- Keep the original filename and relative directory structure in the backup.
|
||||||
|
- Never overwrite the only copy.
|
||||||
|
- Reuse an existing verified backup rather than replacing it with a modified file.
|
||||||
|
- Render candidate output to a temporary path outside the editable image folder first.
|
||||||
|
|
||||||
|
### 3. Inspect every source image
|
||||||
|
|
||||||
|
Record:
|
||||||
|
|
||||||
|
- Width and height.
|
||||||
|
- Format and bit depth.
|
||||||
|
- RGB/RGBA channel layout.
|
||||||
|
- Alpha behavior.
|
||||||
|
- Visible source-language text.
|
||||||
|
- Repeated panel geometry.
|
||||||
|
- Character art and other protected regions.
|
||||||
|
|
||||||
|
Create a contact sheet when multiple variants share a layout. View each target at original
|
||||||
|
resolution before choosing coordinates.
|
||||||
|
|
||||||
|
### 4. Inspect runtime drawing logic
|
||||||
|
|
||||||
|
Search the read-only game project for every image filename and the code or event data that
|
||||||
|
displays it. Determine:
|
||||||
|
|
||||||
|
- Image position, scale, and runtime opacity.
|
||||||
|
- Runtime-drawn counters, percentages, names, or other values.
|
||||||
|
- Exact coordinates, font sizes, and alignment of dynamic text.
|
||||||
|
- Variant-selection logic.
|
||||||
|
- Whether static text is compared or read back elsewhere.
|
||||||
|
|
||||||
|
Treat runtime value locations as protected keepout regions. Do not bake values into the bitmap.
|
||||||
|
If runtime behavior cannot be established, preserve suspicious blank areas and report the
|
||||||
|
uncertainty.
|
||||||
|
|
||||||
|
### 5. Transcribe and translate
|
||||||
|
|
||||||
|
List each visible source string with its role and location. Use this precedence:
|
||||||
|
|
||||||
|
1. Authoritative project vocabulary.
|
||||||
|
2. Existing approved translations.
|
||||||
|
3. Contextual translation based on the surrounding UI.
|
||||||
|
4. A conservative literal translation when context is limited.
|
||||||
|
|
||||||
|
Preserve:
|
||||||
|
|
||||||
|
- Numbers that are already language-neutral.
|
||||||
|
- Percent signs and other functional symbols.
|
||||||
|
- Placeholder-like text such as `???`.
|
||||||
|
- Runtime value slots.
|
||||||
|
- Intentional capitalization conventions.
|
||||||
|
|
||||||
|
Do not rely on OCR alone. Verify OCR output visually, especially for stylized Japanese fonts.
|
||||||
|
|
||||||
|
### 6. Classify the background
|
||||||
|
|
||||||
|
Choose the least destructive valid removal method.
|
||||||
|
|
||||||
|
| Background | Preferred method |
|
||||||
|
| --- | --- |
|
||||||
|
| Flat UI card | Repaint the complete card |
|
||||||
|
| Rounded panel | Rebuild the rounded panel |
|
||||||
|
| Solid background | Fill the bounded text region |
|
||||||
|
| Simple gradient | Interpolate or clone a clean patch |
|
||||||
|
| Repeating texture | Clone a nearby matching region |
|
||||||
|
| Bokeh/noisy field | Clone and feather a clean patch |
|
||||||
|
| Text over artwork | Use a precise mask only when safe |
|
||||||
|
| Text crossing a character | Skip and report unless the user supplies a safe method |
|
||||||
|
|
||||||
|
Prefer whole-panel reconstruction for structured UI. It avoids blurred remnants and
|
||||||
|
source-glyph ghosts.
|
||||||
|
|
||||||
|
### 7. Define an explicit layout
|
||||||
|
|
||||||
|
For each reconstructed panel, record its bounds, corner radius, fill color, opacity model,
|
||||||
|
border, and shadow.
|
||||||
|
|
||||||
|
For each translated string, record its text, anchor or baseline, maximum permitted bounds,
|
||||||
|
font file and size, fill, outline, shadow, alignment, and minimum padding.
|
||||||
|
|
||||||
|
For each protected region, record its bounds and required policy: pixel-identical, no text, or
|
||||||
|
no overlap.
|
||||||
|
|
||||||
|
Do not improvise coordinates independently for images that share a template. Define a base
|
||||||
|
layout and apply only variant-specific overrides.
|
||||||
|
|
||||||
|
### 8. Reconstruct panels
|
||||||
|
|
||||||
|
Use sampled colors from the original panel when practical. Account for how the game displays
|
||||||
|
the asset:
|
||||||
|
|
||||||
|
- Preserve source alpha behavior when the image contains its own translucency.
|
||||||
|
- An opaque replacement card may blend correctly when the game applies runtime opacity to the
|
||||||
|
whole picture.
|
||||||
|
|
||||||
|
Replace enough of the panel to remove every source glyph. Avoid semi-transparent overlays that
|
||||||
|
leave source text visible beneath them. Do not cover frame borders, portraits, or unrelated
|
||||||
|
decoration.
|
||||||
|
|
||||||
|
### 9. Fit typography
|
||||||
|
|
||||||
|
Use real font metrics. Fit text in this order:
|
||||||
|
|
||||||
|
1. Use a font that matches the original visual weight.
|
||||||
|
2. Prefer a condensed family for narrow UI panels.
|
||||||
|
3. Measure the rendered target string.
|
||||||
|
4. Reduce font size only within a reasonable visual range.
|
||||||
|
5. Wrap only when the UI clearly permits multiple lines.
|
||||||
|
6. Shorten wording only when meaning remains accurate.
|
||||||
|
7. Skip or request review if no safe fit exists.
|
||||||
|
|
||||||
|
Use outlines or shadows consistent with the original asset. Preserve character-name colors and
|
||||||
|
other meaningful visual distinctions.
|
||||||
|
|
||||||
|
### 10. Protect dynamic values
|
||||||
|
|
||||||
|
Reserve the exact runtime-drawn areas discovered from source code or event data. Test likely
|
||||||
|
maximum values such as `0`, `99`, `100`, `999`, and any known project-specific maximum. Ensure
|
||||||
|
translated labels and suffixes cannot collide with right-aligned or centered runtime values.
|
||||||
|
|
||||||
|
### 11. Render candidates
|
||||||
|
|
||||||
|
Render from the verified original or backup, never from an earlier candidate. Prefer a
|
||||||
|
deterministic raster backend such as ImageMagick, Pillow, OpenCV, or Skia. Keep operations
|
||||||
|
explicit and repeatable.
|
||||||
|
|
||||||
|
### 12. Validate before installation
|
||||||
|
|
||||||
|
Perform all applicable checks:
|
||||||
|
|
||||||
|
- Decode the candidate successfully.
|
||||||
|
- Confirm exact expected dimensions.
|
||||||
|
- Confirm format, bit depth, and channel layout.
|
||||||
|
- Confirm alpha remains present when required.
|
||||||
|
- Compare protected crops against the original with a zero-pixel-difference target.
|
||||||
|
- Confirm changed pixels remain inside approved regions.
|
||||||
|
- Inspect every variant in a contact sheet.
|
||||||
|
- Inspect important images at original resolution.
|
||||||
|
- Simulate dynamic values at their runtime coordinates.
|
||||||
|
- Check for clipping, ghosted source glyphs, bad baselines, and collisions.
|
||||||
|
- Verify the candidate came from the original, not another modified output.
|
||||||
|
|
||||||
|
Do not install a candidate that fails a hard check.
|
||||||
|
|
||||||
|
### 13. Install atomically
|
||||||
|
|
||||||
|
After validation:
|
||||||
|
|
||||||
|
- Copy or move the candidate over its matching file in the editable image folder.
|
||||||
|
- Preserve the backup outside that folder.
|
||||||
|
- Re-run structural checks on the installed file.
|
||||||
|
- Hash the installed result when reproducibility matters.
|
||||||
|
|
||||||
|
### 14. Report concisely
|
||||||
|
|
||||||
|
Report:
|
||||||
|
|
||||||
|
- Files changed.
|
||||||
|
- Number of translated text elements.
|
||||||
|
- Representative `before -> after` examples.
|
||||||
|
- Preserved dynamic or ambiguous elements.
|
||||||
|
- Backup location.
|
||||||
|
- Validation performed.
|
||||||
|
|
||||||
|
Do not paste full image files, large encoded data, or unrelated source code.
|
||||||
|
|
||||||
|
## Hard safety rules
|
||||||
|
|
||||||
|
- Never alter a portrait or protected artwork merely to make text removal easier.
|
||||||
|
- Never bake runtime counters or percentages into a static asset.
|
||||||
|
- Never use a translucent cover that leaves readable source glyphs beneath it.
|
||||||
|
- Never assume blank space is unused when project source can be inspected.
|
||||||
|
- Never substitute a font silently when exact layout depends on it.
|
||||||
|
- Never install before reviewing the actual rendered candidate.
|
||||||
|
- Never overwrite the only original.
|
||||||
|
- Never claim pixel preservation without running a pixel comparison.
|
||||||
|
- Never modify files outside the editable image folder except for isolated backup and temporary
|
||||||
|
validation artifacts.
|
||||||
|
|
||||||
|
## Decision rules
|
||||||
|
|
||||||
|
Use whole-panel reconstruction when text is contained in a reproducible UI card, several labels
|
||||||
|
share one panel, or localized inpainting would leave artifacts.
|
||||||
|
|
||||||
|
Use localized patch replacement when the background is simple and panel reconstruction would
|
||||||
|
alter too much, or a clean neighboring texture can be cloned safely.
|
||||||
|
|
||||||
|
Skip and request review when:
|
||||||
|
|
||||||
|
- Text overlaps a character, unique illustration detail, or irregular border.
|
||||||
|
- Runtime coordinates cannot be established and collision risk is meaningful.
|
||||||
|
- Translation cannot fit without materially changing meaning.
|
||||||
|
- Alpha behavior is uncertain and the asset is composited at runtime.
|
||||||
|
|
||||||
|
## Completion criteria
|
||||||
|
|
||||||
|
Consider the task complete only when:
|
||||||
|
|
||||||
|
- All source strings in scope have a translated, preserved, skipped, or review disposition.
|
||||||
|
- Every installed bitmap passes structural validation.
|
||||||
|
- Protected regions pass their required pixel checks.
|
||||||
|
- Dynamic values have adequate space.
|
||||||
|
- Visual inspection finds no clipping or source-text ghosts.
|
||||||
|
- Originals remain recoverable.
|
||||||
|
|
@ -14,6 +14,7 @@ from PyQt5.QtCore import (
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import QColor, QDesktopServices, QIcon, QPixmap
|
from PyQt5.QtGui import QColor, QDesktopServices, QIcon, QPixmap
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
|
|
@ -31,6 +32,7 @@ from PyQt5.QtWidgets import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from util.paths import APP_NAME, ORG_NAME
|
from util.paths import APP_NAME, ORG_NAME
|
||||||
|
from util.skills import load_clipboard_skill
|
||||||
|
|
||||||
from util.rpgmaker_images import (
|
from util.rpgmaker_images import (
|
||||||
ImageAsset,
|
ImageAsset,
|
||||||
|
|
@ -297,6 +299,12 @@ class RPGMakerImageManager(QWidget):
|
||||||
"editable img root."
|
"editable img root."
|
||||||
)
|
)
|
||||||
self.open_workspace_button.clicked.connect(self._open_editable_folder)
|
self.open_workspace_button.clicked.connect(self._open_editable_folder)
|
||||||
|
self.copy_translation_button = QPushButton("Copy skill")
|
||||||
|
self.copy_translation_button.setToolTip(
|
||||||
|
"Copy an agent-ready bitmap translation skill scoped to every PNG in the editable "
|
||||||
|
"image folder. Paste it into Codex, Cursor, Copilot, or a similar coding agent."
|
||||||
|
)
|
||||||
|
self.copy_translation_button.clicked.connect(self._copy_translation_skill)
|
||||||
self.decrypt_selected_button = QPushButton("Decrypt")
|
self.decrypt_selected_button = QPushButton("Decrypt")
|
||||||
self.decrypt_selected_button.clicked.connect(self._decrypt_checked)
|
self.decrypt_selected_button.clicked.connect(self._decrypt_checked)
|
||||||
self.decrypt_all_button = QPushButton("Decrypt all")
|
self.decrypt_all_button = QPushButton("Decrypt all")
|
||||||
|
|
@ -319,6 +327,7 @@ class RPGMakerImageManager(QWidget):
|
||||||
self.prepare_button.clicked.connect(self._prepare_checked)
|
self.prepare_button.clicked.connect(self._prepare_checked)
|
||||||
action_buttons = (
|
action_buttons = (
|
||||||
self.open_workspace_button,
|
self.open_workspace_button,
|
||||||
|
self.copy_translation_button,
|
||||||
self.decrypt_selected_button,
|
self.decrypt_selected_button,
|
||||||
self.decrypt_all_button,
|
self.decrypt_all_button,
|
||||||
self.remove_button,
|
self.remove_button,
|
||||||
|
|
@ -426,9 +435,9 @@ class RPGMakerImageManager(QWidget):
|
||||||
def _scan_done(self, generation: int, assets: list[ImageAsset]) -> None:
|
def _scan_done(self, generation: int, assets: list[ImageAsset]) -> None:
|
||||||
if generation != self._scan_generation:
|
if generation != self._scan_generation:
|
||||||
return
|
return
|
||||||
self._set_actions_enabled(True)
|
|
||||||
self.assets = assets
|
self.assets = assets
|
||||||
self.assets_by_id = {asset.asset_id: asset for asset in assets}
|
self.assets_by_id = {asset.asset_id: asset for asset in assets}
|
||||||
|
self._set_actions_enabled(True)
|
||||||
self.selected_ids.intersection_update(self.assets_by_id)
|
self.selected_ids.intersection_update(self.assets_by_id)
|
||||||
self.folder_combo.blockSignals(True)
|
self.folder_combo.blockSignals(True)
|
||||||
current_folder = self.folder_combo.currentData() or ""
|
current_folder = self.folder_combo.currentData() or ""
|
||||||
|
|
@ -632,6 +641,49 @@ class RPGMakerImageManager(QWidget):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
QMessageBox.warning(self, "Editable Image Folder", str(exc))
|
QMessageBox.warning(self, "Editable Image Folder", str(exc))
|
||||||
|
|
||||||
|
def _editable_image_root(self) -> Path:
|
||||||
|
if self.game_root is None:
|
||||||
|
raise ValueError("Select a game folder first.")
|
||||||
|
root = Path(self.game_root).expanduser().resolve()
|
||||||
|
content_relative = resolve_content_root(root).relative_to(root)
|
||||||
|
return editable_workspace_root(root) / content_relative / "img"
|
||||||
|
|
||||||
|
def _copy_translation_skill(self) -> None:
|
||||||
|
"""Copy the bitmap-localization skill with paths for this project."""
|
||||||
|
editable_assets = self._editable_assets()
|
||||||
|
if not editable_assets:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"No Editable Images",
|
||||||
|
"Decrypt one or more images before copying the translation skill.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if self.game_root is None:
|
||||||
|
raise ValueError("Select a game folder first.")
|
||||||
|
game_root = Path(self.game_root).expanduser().resolve()
|
||||||
|
replacements = {
|
||||||
|
"{{GAME_ROOT}}": str(game_root),
|
||||||
|
"{{EDITABLE_IMAGES_FOLDER}}": str(self._editable_image_root().resolve()),
|
||||||
|
"{{VOCAB_FILE}}": str(game_root / "vocab.txt"),
|
||||||
|
}
|
||||||
|
prompt = load_clipboard_skill("image_translation.md")
|
||||||
|
missing = [token for token in replacements if token not in prompt]
|
||||||
|
if missing:
|
||||||
|
raise ValueError(
|
||||||
|
"Image translation skill is missing required placeholder(s): "
|
||||||
|
+ ", ".join(missing)
|
||||||
|
)
|
||||||
|
for token, value in replacements.items():
|
||||||
|
prompt = prompt.replace(token, value)
|
||||||
|
QApplication.clipboard().setText(prompt)
|
||||||
|
self.status_label.setText(
|
||||||
|
f"Copied image translation skill for {len(editable_assets):,} editable PNG(s): "
|
||||||
|
f"{self._editable_image_root()}"
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
QMessageBox.warning(self, "Copy Image Translation Skill", str(exc))
|
||||||
|
|
||||||
def _selected_assets(self) -> list[ImageAsset]:
|
def _selected_assets(self) -> list[ImageAsset]:
|
||||||
return [self.assets_by_id[key] for key in self.selected_ids if key in self.assets_by_id]
|
return [self.assets_by_id[key] for key in self.selected_ids if key in self.assets_by_id]
|
||||||
|
|
||||||
|
|
@ -794,12 +846,14 @@ class RPGMakerImageManager(QWidget):
|
||||||
def _set_actions_enabled(self, enabled: bool) -> None:
|
def _set_actions_enabled(self, enabled: bool) -> None:
|
||||||
for button in (
|
for button in (
|
||||||
self.open_workspace_button,
|
self.open_workspace_button,
|
||||||
|
self.copy_translation_button,
|
||||||
self.decrypt_selected_button,
|
self.decrypt_selected_button,
|
||||||
self.decrypt_all_button,
|
self.decrypt_all_button,
|
||||||
self.remove_button,
|
self.remove_button,
|
||||||
self.prepare_button,
|
self.prepare_button,
|
||||||
):
|
):
|
||||||
button.setEnabled(enabled)
|
button.setEnabled(enabled)
|
||||||
|
self.copy_translation_button.setEnabled(enabled and bool(self._editable_assets()))
|
||||||
|
|
||||||
def closeEvent(self, event) -> None:
|
def closeEvent(self, event) -> None:
|
||||||
running = [
|
running = [
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,17 @@ class SkillsTab(QWidget):
|
||||||
hint="VX Ace Ruby script localisation audit and approved in-place translation prompt.",
|
hint="VX Ace Ruby script localisation audit and approved in-place translation prompt.",
|
||||||
is_json=False,
|
is_json=False,
|
||||||
)
|
)
|
||||||
|
self._add_file_page(
|
||||||
|
key="image_translation",
|
||||||
|
tab_title="Image TL",
|
||||||
|
path=SKILLS_DIR / "image_translation.md",
|
||||||
|
hint=(
|
||||||
|
"Clipboard skill copied from the Image Manager for deterministic bitmap UI "
|
||||||
|
"localisation. Keep the {{GAME_ROOT}}, {{EDITABLE_IMAGES_FOLDER}}, and "
|
||||||
|
"{{VOCAB_FILE}} placeholders."
|
||||||
|
),
|
||||||
|
is_json=False,
|
||||||
|
)
|
||||||
self._add_file_page(
|
self._add_file_page(
|
||||||
key="risky_codes",
|
key="risky_codes",
|
||||||
tab_title="Risky Codes",
|
tab_title="Risky Codes",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ _SHIPPED_DATA_FILES = (
|
||||||
"data/skills/wrap_config.md",
|
"data/skills/wrap_config.md",
|
||||||
"data/skills/plugin_translation.md",
|
"data/skills/plugin_translation.md",
|
||||||
"data/skills/ace_script_translation.md",
|
"data/skills/ace_script_translation.md",
|
||||||
|
"data/skills/image_translation.md",
|
||||||
"data/skills/risky_codes.md",
|
"data/skills/risky_codes.md",
|
||||||
"data/skills/wolf_speakers.md",
|
"data/skills/wolf_speakers.md",
|
||||||
"data/help/index.json",
|
"data/help/index.json",
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
|
||||||
def test_bottom_controls_share_one_row_and_action_width(self):
|
def test_bottom_controls_share_one_row_and_action_width(self):
|
||||||
action_buttons = (
|
action_buttons = (
|
||||||
self.manager.open_workspace_button,
|
self.manager.open_workspace_button,
|
||||||
|
self.manager.copy_translation_button,
|
||||||
self.manager.decrypt_selected_button,
|
self.manager.decrypt_selected_button,
|
||||||
self.manager.decrypt_all_button,
|
self.manager.decrypt_all_button,
|
||||||
self.manager.remove_button,
|
self.manager.remove_button,
|
||||||
|
|
@ -250,6 +251,44 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
|
||||||
self.manager.open_workspace_button.geometry().top(),
|
self.manager.open_workspace_button.geometry().top(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_translation_skill_is_enabled_only_for_editable_images(self):
|
||||||
|
self.assertFalse(self.manager.copy_translation_button.isEnabled())
|
||||||
|
|
||||||
|
asset = self.manager.assets[0]
|
||||||
|
asset.plain_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
asset.plain_path.write_bytes(asset.runtime_plain_path.read_bytes())
|
||||||
|
self.manager._start_scan()
|
||||||
|
self.manager._scan_worker.wait(5000)
|
||||||
|
self.app.processEvents()
|
||||||
|
|
||||||
|
self.assertTrue(self.manager.copy_translation_button.isEnabled())
|
||||||
|
|
||||||
|
def test_translation_skill_copies_project_specific_editable_folder(self):
|
||||||
|
asset = self.manager.assets[0]
|
||||||
|
asset.plain_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
asset.plain_path.write_bytes(asset.runtime_plain_path.read_bytes())
|
||||||
|
self.manager._start_scan()
|
||||||
|
self.manager._scan_worker.wait(5000)
|
||||||
|
self.app.processEvents()
|
||||||
|
|
||||||
|
QApplication.clipboard().clear()
|
||||||
|
self.manager.copy_translation_button.click()
|
||||||
|
prompt = QApplication.clipboard().text()
|
||||||
|
|
||||||
|
self.assertIn(str(self.game_root), prompt)
|
||||||
|
self.assertIn(str(self.game_root / ".dazedtl" / "images" / "img"), prompt)
|
||||||
|
self.assertIn(str(self.game_root / "vocab.txt"), prompt)
|
||||||
|
self.assertIn("authoritative glossary for every translation", prompt)
|
||||||
|
self.assertIn("deterministic raster backend", prompt)
|
||||||
|
self.assertIn("runtime value locations as protected keepout regions", prompt)
|
||||||
|
self.assertNotIn("{{GAME_ROOT}}", prompt)
|
||||||
|
self.assertNotIn("{{EDITABLE_IMAGES_FOLDER}}", prompt)
|
||||||
|
self.assertNotIn("{{VOCAB_FILE}}", prompt)
|
||||||
|
self.assertIn(
|
||||||
|
"Copied image translation skill for 1 editable PNG",
|
||||||
|
self.manager.status_label.text(),
|
||||||
|
)
|
||||||
|
|
||||||
def test_open_folder_uses_highlighted_images_editable_parent(self):
|
def test_open_folder_uses_highlighted_images_editable_parent(self):
|
||||||
self._click(0)
|
self._click(0)
|
||||||
asset_id = self.manager.image_list.currentItem().data(Qt.UserRole + 1)
|
asset_id = self.manager.image_list.currentItem().data(Qt.UserRole + 1)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class WorkflowTranslationPromptTests(unittest.TestCase):
|
||||||
"wrap_config.md",
|
"wrap_config.md",
|
||||||
"plugin_translation.md",
|
"plugin_translation.md",
|
||||||
"ace_script_translation.md",
|
"ace_script_translation.md",
|
||||||
|
"image_translation.md",
|
||||||
"risky_codes.md",
|
"risky_codes.md",
|
||||||
"wolf_speakers.md",
|
"wolf_speakers.md",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue