refactor(skills): align MPI skills with Agent Skills best practices
The skill metadata had drifted: every SKILL.md name field lacked the mpi- prefix, contradicting the directory names and the Agent Skills specification. Descriptions were also missing negative triggers, making it easy for the agent to load the wrong skill. Rewrote the pdf-to-docx skill to follow progressive disclosure: the main SKILL.md dropped from 318 lines to 80, with detailed code examples moved to on-demand references. Added uv run instructions and /// script PEP 723 metadata so dependencies are declared inline and installed automatically. Fixed the pptx skill's script paths and added CLI usage messages to both pptx scripts and the normalize script. Removed the empty self-review directory that was superseded by the unified translation-review skill.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
---
|
||||
name: pptx-translate
|
||||
description: Translate PowerPoint files between Chinese and English — extract strings to YAML, translate, quality review, and write back with font-shrink + auto-fit for layout.
|
||||
name: mpi-pptx-translate
|
||||
description: Translate PowerPoint files between Chinese and English — extract strings to YAML, translate, quality review, and write back with font-shrink + auto-fit for layout. Use only for .pptx files. Do not use for .ppt, Google Slides exports, or PDFs.
|
||||
category: productivity
|
||||
compatibility: Requires Python 3.9+ and uv. Dependencies (python-pptx, pyyaml) are declared in the scripts' /// script metadata.
|
||||
---
|
||||
|
||||
# PPTX Translation
|
||||
@@ -12,7 +13,13 @@ Translate `.pptx` files between Chinese and English. Covers the full pipeline: e
|
||||
|
||||
### 1. Extract strings to YAML
|
||||
|
||||
Run `toolkit/scripts/extract.py original.pptx strings.yaml`. Produces YAML with entries:
|
||||
Run with `uv`:
|
||||
|
||||
```bash
|
||||
uv run skills/mpi-pptx-translate/scripts/extract.py original.pptx strings.yaml
|
||||
```
|
||||
|
||||
`uv` reads the `/// script` metadata block and installs `python-pptx` and `pyyaml` automatically. Produces YAML with entries:
|
||||
|
||||
```yaml
|
||||
- slide: 1
|
||||
@@ -57,7 +64,9 @@ Scan for:
|
||||
|
||||
### 4. Write back with layout fixes
|
||||
|
||||
Run `toolkit/scripts/build.py strings.yaml original.pptx translated.pptx`.
|
||||
```bash
|
||||
uv run skills/mpi-pptx-translate/scripts/build.py strings.yaml original.pptx translated.pptx
|
||||
```
|
||||
|
||||
The script:
|
||||
- Replaces text in matching paragraphs (clears all runs, sets first run)
|
||||
@@ -80,5 +89,5 @@ The absorbed `pptx-translation` skill had alternate script names: `extract_pptx.
|
||||
|
||||
## Scripts
|
||||
|
||||
- `toolkit/scripts/extract.py` — extract strings from PPTX to YAML
|
||||
- `toolkit/scripts/build.py` — write translations back with font shrink + auto-fit
|
||||
- `uv run skills/mpi-pptx-translate/scripts/extract.py` — extract strings from PPTX to YAML
|
||||
- `uv run skills/mpi-pptx-translate/scripts/build.py` — write translations back with font shrink + auto-fit
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.9"
|
||||
# dependencies = [
|
||||
# "python-pptx",
|
||||
# "pyyaml",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import sys, yaml
|
||||
from pptx import Presentation
|
||||
from pptx.util import Pt
|
||||
@@ -5,15 +13,6 @@ from pptx.enum.text import MSO_AUTO_SIZE
|
||||
|
||||
FONT_SCALE = 0.82 # shrink ~18%
|
||||
|
||||
yaml_path, src_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
with open(yaml_path) as f:
|
||||
entries = yaml.safe_load(f)
|
||||
|
||||
index = {}
|
||||
for e in entries:
|
||||
index[(e["slide"], e["shape"], e["run"])] = e["en"]
|
||||
|
||||
|
||||
def shrink_font_tf(tf):
|
||||
for para in tf.paragraphs:
|
||||
@@ -26,43 +25,58 @@ def shrink_font_tf(tf):
|
||||
pass
|
||||
|
||||
|
||||
prs = Presentation(src_path)
|
||||
def build(yaml_path, src_path, out_path):
|
||||
with open(yaml_path) as f:
|
||||
entries = yaml.safe_load(f)
|
||||
|
||||
for slide_num, slide in enumerate(prs.slides, 1):
|
||||
for shape_idx, shape in enumerate(slide.shapes):
|
||||
if shape.has_text_frame:
|
||||
has_translation = False
|
||||
for para_idx, para in enumerate(shape.text_frame.paragraphs):
|
||||
key = (slide_num, shape_idx, para_idx)
|
||||
if key in index:
|
||||
has_translation = True
|
||||
en = index[key]
|
||||
for r in para.runs:
|
||||
r.text = ""
|
||||
if para.runs:
|
||||
para.runs[0].text = en
|
||||
else:
|
||||
para.add_run().text = en
|
||||
if has_translation:
|
||||
shrink_font_tf(shape.text_frame)
|
||||
index = {}
|
||||
for e in entries:
|
||||
index[(e["slide"], e["shape"], e["run"])] = e["en"]
|
||||
|
||||
elif shape.has_table:
|
||||
num_rows = len(shape.table.rows)
|
||||
for r in range(num_rows * len(shape.table.columns)):
|
||||
key = (slide_num, shape_idx, r)
|
||||
if key in index:
|
||||
row = r % num_rows
|
||||
col = r // num_rows
|
||||
shape.table.cell(row, col).text = index[key]
|
||||
for row in shape.table.rows:
|
||||
for cell in row.cells:
|
||||
shrink_font_tf(cell.text_frame)
|
||||
prs = Presentation(src_path)
|
||||
|
||||
key = (slide_num, -1, 0)
|
||||
if key in index and slide.has_notes_slide:
|
||||
ns = slide.notes_slide
|
||||
ns.notes_text_frame.clear()
|
||||
ns.notes_text_frame.paragraphs[0].add_run().text = index[key]
|
||||
for slide_num, slide in enumerate(prs.slides, 1):
|
||||
for shape_idx, shape in enumerate(slide.shapes):
|
||||
if shape.has_text_frame:
|
||||
has_translation = False
|
||||
for para_idx, para in enumerate(shape.text_frame.paragraphs):
|
||||
key = (slide_num, shape_idx, para_idx)
|
||||
if key in index:
|
||||
has_translation = True
|
||||
en = index[key]
|
||||
for r in para.runs:
|
||||
r.text = ""
|
||||
if para.runs:
|
||||
para.runs[0].text = en
|
||||
else:
|
||||
para.add_run().text = en
|
||||
if has_translation:
|
||||
shrink_font_tf(shape.text_frame)
|
||||
|
||||
prs.save(out_path)
|
||||
print(f"Saved {out_path}")
|
||||
elif shape.has_table:
|
||||
num_rows = len(shape.table.rows)
|
||||
for r in range(num_rows * len(shape.table.columns)):
|
||||
key = (slide_num, shape_idx, r)
|
||||
if key in index:
|
||||
row = r % num_rows
|
||||
col = r // num_rows
|
||||
shape.table.cell(row, col).text = index[key]
|
||||
for row in shape.table.rows:
|
||||
for cell in row.cells:
|
||||
shrink_font_tf(cell.text_frame)
|
||||
|
||||
key = (slide_num, -1, 0)
|
||||
if key in index and slide.has_notes_slide:
|
||||
ns = slide.notes_slide
|
||||
ns.notes_text_frame.clear()
|
||||
ns.notes_text_frame.paragraphs[0].add_run().text = index[key]
|
||||
|
||||
prs.save(out_path)
|
||||
print(f"Saved {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: uv run build.py <strings.yaml> <input.pptx> <output.pptx>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
build(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.9"
|
||||
# dependencies = [
|
||||
# "python-pptx",
|
||||
# "pyyaml",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import sys, yaml
|
||||
from pptx import Presentation
|
||||
|
||||
@@ -62,6 +70,9 @@ def extract(pptx_path):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: uv run extract.py <input.pptx> <output.yaml>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
entries = extract(sys.argv[1])
|
||||
with open(sys.argv[2], "w") as f:
|
||||
yaml.dump(entries, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
||||
|
||||
Reference in New Issue
Block a user