The interface between the PPTX converter and everything that localizes a deck (translate CLI, language switcher, review view). Version 1.
The converter emits one source table per deck. A translation is a separate file keyed by string ID. The slide HTML never changes per language; the viewer swaps text by ID.
decks/<slug>/
├── slides.html generated; every localizable node carries data-i18n="<id>"
├── strings.json source table (written by the converter)
└── strings.<lang>.json one per target language (written by the translate CLI)
{
"version": 1,
"sourceLang": "en",
"strings": [
{ "id": "s1.sh2.p1", "slide": 1, "kind": "text", "text": "Decks as <b>Code</b>" }
]
}| Field | Notes |
|---|---|
id |
Unique, stable key, see below. |
slide |
1-based position in presentation order. |
kind |
text, table, notes, alt. Lets translators treat alt text and notes differently. |
text |
One whole paragraph as an HTML fragment. Never a single run. |
context |
Optional. Short hint for translators, e.g. the slide title. Never rendered. |
Strings are listed in document order: slide by slide, shapes in z-order, then notes. Empty paragraphs are not emitted.
| Pattern | Source |
|---|---|
s<slide>.sh<shapeId>.p<n> |
Paragraph n (1-based) of the shape whose cNvPr id is shapeId |
s<slide>.sh<shapeId>.r<row>c<col>.p<n> |
Paragraph in a table cell |
s<slide>.sh<shapeId>.alt |
Alt text of a shape or picture |
s<slide>.L.sh<shapeId>.p<n> |
Text from a layout shape drawn on that slide (logos, labels) |
s<slide>.M.sh<shapeId>.p<n> |
Same, from the slide master |
s<slide>.notes.p<n> |
Speaker note paragraph |
Shape IDs come from the PPTX, so they survive reordering shapes within a slide. Slide numbers do not survive reordering slides; re-running the converter after reordering means re-translating.
Paragraphs set entirely in a monospace font are code. They stay in the HTML but are left out of the table, so nothing translates them.
The only tags allowed are <b>, <i>, <u>, <sup>, <sub>, <code>, <br>
and <a href="...">. Everything else is escaped (<, >, &).
Translators must keep every tag, in matching pairs, and must not change href
values. Tag order may change when word order does. A translation that fails this
check is invalid.
{
"version": 1,
"lang": "de",
"sourceLang": "en",
"strings": {
"s1.sh2.p1": { "text": "Decks als <b>Code</b>", "src": "9f3a1c2e" }
}
}langis a BCP 47 tag (de,pt-BR,ar).srcis the hash of the sourcetextthe translation was made from: FNV-1a 32-bit over the UTF-8 bytes, lowercase hex, 8 characters. It is not cryptographic. When the source text changes,srcno longer matches and that one string is stale and gets re-translated; the rest are kept.- A missing or stale string falls back to the source text in the viewer. The deck never renders blank.
- Right-to-left languages (
ar,he,fa,ur) setdir="rtl"on the deck root, along withlang.
slides.html marks every translatable node so a viewer can swap text without
knowing anything about PowerPoint:
| Marker | Meaning |
|---|---|
data-i18n="<id>" |
Replace this element's contents with the string's text |
data-i18n-alt="<id>" |
Replace this image's alt with the string's text, tags removed |
data-nt |
Code or other text that is deliberately not translated |
assets/deck-i18n.js does the swap (DeckI18n.apply(table, lang)) and rebuilds
each translation from the allowed tags only, so a table cannot inject markup.
deck.html?d=<slug>&lang=de and the standalone file's ?lang=de both use it.
Longer text is expected: after a swap, any box whose text no longer fits is
shrunk (down to 60%) so it does not run over its neighbours. Add &review to the
address to outline boxes that still overflow.
- Never edit
strings.jsonby hand. Fix the PPTX and re-run the converter. - Validate before accepting a translation: same ID set (extras are errors),
matching tags and
hrefs, non-emptytext. - Text inside images, and charts rendered as images, is not in the table and is not translated.
See docs/examples/ for a small matching pair.