Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ decks-as-code/
│ ├── build-manifest.mjs regenerates decks.json
│ ├── build-site.mjs builds dist/: offline site plus single-file decks
│ ├── import-pptx.mjs converts a .pptx into a Markdown deck folder
│ └── pptx-to-html.mjs converts a .pptx into an HTML deck folder (pptx-html.mjs)
│ ├── pptx-to-html.mjs converts a .pptx into an HTML deck folder (pptx-html.mjs)
│ ├── translate.mjs translates a deck's strings.json with an LLM
│ ├── translate-core.mjs hashing, validation, batching, retry (used by translate)
│ └── llm-provider.mjs provider interface: copilot, openai-compatible, mock
└── decks/
└── <slug>/
├── slides.md the deck. The only file you edit day to day.
Expand Down Expand Up @@ -166,7 +169,8 @@ Open `convert.html` on the served site (or `node tools/pptx-to-html.mjs talk.ppt
The folder holds `slides.html`, `strings.json` and `images/`. `strings.json` lists
every paragraph, speaker note and alt text under a stable ID, in the format described
in [`docs/string-table.md`](docs/string-table.md); translations are `strings.<lang>.json`
files in the same folder, and `?lang=<code>` on the deck or standalone file shows one.
files in the same folder. A language picker appears in the corner of the deck (and the
single-file version) when translations exist; `?lang=<code>` opens one directly.
Longer translated text shrinks to fit its box (down to 60%); add `&review` to the
address to outline boxes that still overflow.

Expand All @@ -185,6 +189,47 @@ table, so nothing translates them.

---

## Translate a deck

`tools/translate.mjs` translates a deck's string table (`decks/<slug>/strings.json`,
see [`docs/string-table.md`](docs/string-table.md)) into `strings.<lang>.json` files.
Node 18+, no dependencies.

```powershell
node tools\translate.mjs my-talk --to de,pt-BR,ar # translate
node tools\translate.mjs my-talk --to de --dry-run # show what would change
node tools\translate.mjs my-talk --to de --force # retranslate everything
node tools\translate.mjs my-talk --to de --provider mock # offline fake, no LLM
node --test # run the tests (no network)
```

- **Incremental.** Only strings that are missing or whose source text changed (the
`src` hash no longer matches) are sent to the model. `--force` redoes all of them.
- **Validated.** Every translation must keep the same inline tags (in matching pairs),
keep `href` values, and be non-empty. A failing batch is retried once with the error
fed back; strings that still fail are skipped and listed, the file stays valid, and
the viewer falls back to the source text for them. Exit code `2` means "done, but
some strings were skipped"; re-run to retry them.
- **Provider.** `--provider copilot` (default) runs the GitHub Copilot CLI in
non-interactive mode, so it uses the models in your Copilot subscription: no API key,
nothing to configure beyond signing in once by running `copilot`. Tools, MCP servers and
custom instructions are switched off, so the model can only answer. Optional:
`--model <name>` (otherwise the CLI's default), `LLM_REASONING_EFFORT` (default `low`),
`COPILOT_BIN` if `copilot` is not on your PATH. The five-slide sample takes under a
minute per language; a 50-slide deck takes a few minutes.
`--provider openai` talks to any OpenAI-compatible
`/chat/completions` endpoint. Configure it with environment variables (the key is
never a flag, never logged, never written): `LLM_API_KEY` (or `OPENAI_API_KEY`),
`LLM_MODEL` (or `--model`), and optionally `LLM_BASE_URL` (or `--base-url`; default
`https://api.openai.com/v1`). For Azure OpenAI / Foundry use the
`https://<resource>.openai.azure.com/openai/v1` base URL, the deployment name as the
model, and `LLM_AUTH_HEADER=api-key`. Set `LLM_JSON_MODE=off` if a host rejects
`response_format`. 429s honour `retry-after`. Providers live in
`tools/llm-provider.mjs`; adding one means returning `{ name, complete({messages}) }`.
GitHub Models was retired on 2026-07-30, so it is not supported.

---

## Writing slides

`slides.md` is ordinary Markdown with three separator conventions:
Expand Down
57 changes: 56 additions & 1 deletion assets/deck-i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,62 @@
if (reveal) fitSlide(reveal.getCurrentSlide());
}

function languageName(code) {
try {
var label = new Intl.DisplayNames([code], { type: 'language' }).of(code);
return label ? label.charAt(0).toUpperCase() + label.slice(1) : code;
} catch (e) {
return code;
}
}

/**
* A language <select> in the corner of the deck. Hidden when there is nothing
* to choose. Options are named in their own language ("Français"), the source
* language comes first, and the choice is mirrored into ?lang= so the address
* can be shared.
*
* source the deck's own language tag
* languages translation tags that have a table
* load async (tag) => table
* reveal the Reveal instance, to re-fit text after a swap
*/
function mountPicker(o) {
var langs = (o.languages || []).filter(function (l) { return l !== o.source; });
if (!langs.length) return null;

var box = document.createElement('div');
box.className = 'deck-lang';
var select = document.createElement('select');
select.setAttribute('aria-label', 'Language');
[o.source].concat(langs).forEach(function (tag) {
var opt = document.createElement('option');
opt.value = tag;
opt.textContent = languageName(tag === o.source ? tag.split('-')[0] : tag);
select.appendChild(opt);
});
box.appendChild(select);
document.body.appendChild(box);

var chosen = langFromUrl();
select.value = chosen && langs.indexOf(chosen) >= 0 ? chosen : o.source;

select.addEventListener('change', async function () {
var tag = select.value;
var table = null;
if (tag !== o.source) {
try { table = await o.load(tag); } catch (e) { table = null; }
}
apply(table, tag);
if (o.reveal) refit(o.reveal);
var url = new URL(window.location.href);
if (tag === o.source) url.searchParams.delete('lang'); else url.searchParams.set('lang', tag);
window.history.replaceState(null, '', url);
});
return select;
}

window.DeckI18n = {
apply: apply, safeFragment: safeFragment, langFromUrl: langFromUrl, fitSlide: fitSlide, autoFit: autoFit, refit: refit,
apply: apply, safeFragment: safeFragment, langFromUrl: langFromUrl, fitSlide: fitSlide, autoFit: autoFit, refit: refit, mountPicker: mountPicker,
};
})();
28 changes: 28 additions & 0 deletions assets/pptx-slide.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,31 @@
html.deck-review .reveal .pptx-canvas .sh[data-overflow] {
outline: 2px dashed #e5484d;
}

/* Language picker, shown when a deck has translations. */
.deck-lang {
position: fixed;
top: 12px;
right: 16px;
z-index: 30;
}

.deck-lang select {
font: 500 14px/1.2 'Segoe UI', system-ui, -apple-system, sans-serif;
color: #f2f4f8;
background: rgba(16, 19, 26, 0.85);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 8px;
padding: 7px 10px;
cursor: pointer;
}

.deck-lang select:hover,
.deck-lang select:focus {
border-color: #4da3ff;
outline: none;
}

@media print {
.deck-lang { display: none; }
}
23 changes: 23 additions & 0 deletions deck.html
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,32 @@ <h2 style="font-size:1.1rem;margin:2rem 0 .5rem">To run the whole site</h2>
pdfSeparateFragments: false,
});
DeckI18n.autoFit(Reveal);
await mountLanguagePicker(base, meta);
window.DECK_READY = true;
}

/** Offer a language switch when decks.json lists translations for this deck. */
async function mountLanguagePicker(base, meta) {
let languages = [];
try {
const res = await fetch('decks.json', { cache: 'no-cache' });
const slug = new URLSearchParams(window.location.search).get('d');
if (res.ok) languages = ((await res.json()).decks || []).find((d) => d.slug === slug)?.languages || [];
} catch {
/* no picker without a listing */
}
DeckI18n.mountPicker({
source: meta.lang || 'en',
languages,
reveal: Reveal,
load: async (tag) => {
const res = await fetch(`${base}/strings.${encodeURIComponent(tag)}.json`, { cache: 'no-cache' });
if (!res.ok) throw new Error(`No translation for ${tag}`);
return res.json();
},
});
}

/*
* ?lang=de swaps every data-i18n node for its entry in strings.de.json
* (see assets/deck-i18n.js and docs/string-table.md).
Expand Down
7 changes: 6 additions & 1 deletion decks.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
"date": "2026-11-04",
"tags": [],
"theme": "white",
"format": "html"
"format": "html",
"languages": [
"de",
"es",
"fr"
]
},
{
"slug": "decks-as-code",
Expand Down
127 changes: 127 additions & 0 deletions decks/sample-ai-apps/strings.de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"version": 1,
"lang": "de",
"sourceLang": "en-US",
"strings": {
"s1.sh2.p1": { "text": "Generative KI-Apps in Azure entwickeln", "src": "7e7c01f9" },
"s1.sh3.p1": { "text": "Juni 2025", "src": "67047020" },
"s1.sh5.p1": { "text": "https://aka.ms/mslearn-gen-ai-app-dev", "src": "124da598" },
"s1.notes.p1": { "text": "Über den Link auf der Folie gelangen Sie zum Microsoft Learn-Lernpfad, auf dem diese Präsentation basiert.", "src": "a9b54f42" },
"s2.sh5.p1": { "text": "Workloads für künstliche Intelligenz", "src": "783619a6" },
"s2.sh3.p1": { "text": "© Copyright Microsoft Corporation. Alle Rechte vorbehalten.", "src": "eb8425d5" },
"s2.sh32.alt": { "text": "Ausgefülltes Chatsymbol", "src": "ec9a46d3" },
"s2.sh29.alt": { "text": "Ausgefülltes Robotersymbol", "src": "28889807" },
"s2.sh25.p1": { "text": "Generative KI und Agenten", "src": "4e78ef44" },
"s2.sh44.alt": { "text": "Dokumentsymbol als Umriss", "src": "27286e9a" },
"s2.sh40.p1": { "text": "Text und Sprache", "src": "a9eadd37" },
"s2.sh49.alt": { "text": "Callcenter-Symbol als Umriss", "src": "a781af24" },
"s2.sh47.p1": { "text": "Sprachverarbeitung", "src": "771f42be" },
"s2.sh54.alt": { "text": "Ausgefülltes Augenscan-Symbol", "src": "06bb4243" },
"s2.sh52.p1": { "text": "Computer Vision", "src": "23c85bd0" },
"s2.sh60.alt": { "text": "Ausgefülltes Lupensymbol", "src": "d243ec84" },
"s2.sh61.alt": { "text": "Vertragssymbol als Umriss", "src": "31cc5d99" },
"s2.sh57.p1": { "text": "Informationsextraktion", "src": "7c60c39a" },
"s2.sh67.alt": { "text": "Ausgefülltes Zahnradsymbol", "src": "8e4ad78f" },
"s2.sh64.p1": { "text": "Maschinelles Lernen", "src": "9d25e72c" },
"s2.notes.p1": { "text": "Geben Sie den Lernenden eine Orientierung: „Azure AI umfasst mehrere Workload-Kategorien – heute konzentrieren wir uns auf <i>generative KI und Agenten</i>.“", "src": "7c63c722" },
"s2.notes.p2": { "text": "Kurzer Vergleich: klassisches maschinelles Lernen (Vorhersagen, f(x)=y) gegenüber generativer KI (Sprache + Schlussfolgern + Inhaltserstellung).", "src": "c70d5d0b" },
"s3.sh5.p1": { "text": "Unser heutiger Schwerpunkt", "src": "ebfd8520" },
"s3.sh44.alt": { "text": "Dokumentsymbol als Umriss", "src": "27286e9a" },
"s3.sh40.p1": { "text": "Text und Sprache", "src": "a9eadd37" },
"s3.sh49.alt": { "text": "Callcenter-Symbol als Umriss", "src": "a781af24" },
"s3.sh47.p1": { "text": "Sprachverarbeitung", "src": "771f42be" },
"s3.sh54.alt": { "text": "Ausgefülltes Augenscan-Symbol", "src": "06bb4243" },
"s3.sh52.p1": { "text": "Computer Vision", "src": "23c85bd0" },
"s3.sh60.alt": { "text": "Ausgefülltes Lupensymbol", "src": "d243ec84" },
"s3.sh61.alt": { "text": "Vertrag als Umrisssymbol", "src": "31cc5d99" },
"s3.sh57.p1": { "text": "Informationsextraktion", "src": "7c60c39a" },
"s3.sh67.alt": { "text": "Zahnräder mit Volltonfüllung", "src": "8e4ad78f" },
"s3.sh64.p1": { "text": "Maschinelles Lernen", "src": "9d25e72c" },
"s3.sh8.alt": { "text": "Chat mit Volltonfüllung", "src": "ec9a46d3" },
"s3.sh6.alt": { "text": "Roboter mit Volltonfüllung", "src": "28889807" },
"s3.sh9.p1": { "text": "Generative KI und Agenten", "src": "4e78ef44" },
"s3.sh3.p1": { "text": "© Copyright Microsoft Corporation. Alle Rechte vorbehalten.", "src": "eb8425d5" },
"s3.notes.p1": { "text": "Betonen Sie die thematische Eingrenzung: „Wenn Sie wegen Bildverarbeitung, Sprachverarbeitung oder Übersetzung hier sind: Diese Möglichkeiten gibt es auch – aber heute konzentrieren wir uns auf generative KI-Modelle und die Entwicklung von Chat-Anwendungen.“", "src": "3774d615" },
"s3.notes.p2": { "text": "Geben Sie einen Überblick über den Tagesablauf: Plattformgrundlagen → Modelle → Endpunkte/SDKs → Entwicklung von Chat-Anwendungen → Optimierung → verantwortungsvolle KI.", "src": "aa2c688c" },
"s4.sh2.p1": { "text": "Die Entwicklung von KI-Lösungen auf Azure planen und vorbereiten", "src": "b7da702c" },
"s4.sh4.p1": { "text": "https://aka.ms/mslearn-prepare-ai-dev", "src": "71f607d1" },
"s4.notes.p1": { "text": "Über den Link auf der Folie gelangen Sie zum Microsoft Learn-Modul, auf dem diese Lektion basiert.", "src": "073c9933" },
"s5.M.sh37.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh38.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh48.p1": { "text": "73262F", "src": "0182554f" },
"s5.M.sh50.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh51.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh52.p1": { "text": "FFB3BB", "src": "4155e0ae" },
"s5.M.sh55.p1": { "text": "FFFFFF", "src": "4414127d" },
"s5.M.sh57.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh58.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh59.p1": { "text": "FF5C39", "src": "64f573ad" },
"s5.M.sh61.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh62.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh63.p1": { "text": "73391D", "src": "26a09c72" },
"s5.M.sh65.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh66.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh67.p1": { "text": "FFA38B", "src": "be00c8cf" },
"s5.M.sh70.p1": { "text": "F4F3F5", "src": "4f0282d5" },
"s5.M.sh72.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh73.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh74.p1": { "text": "0078D4", "src": "3d9ce272" },
"s5.M.sh76.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh77.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh78.p1": { "text": "2A446F", "src": "87444a50" },
"s5.M.sh80.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh81.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh82.p1": { "text": "8DC8E8", "src": "93ca13c7" },
"s5.M.sh87.p1": { "text": "Blauschwarz", "src": "921a7b2e" },
"s5.M.sh88.p1": { "text": "091F2C", "src": "e2f1513a" },
"s5.M.sh93.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh94.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh95.p1": { "text": "702573", "src": "c11cc7e1" },
"s5.M.sh97.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh98.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh99.p1": { "text": "CD9BCF", "src": "55b6be38" },
"s5.M.sh102.p1": { "text": "000000", "src": "68d6565d" },
"s5.M.sh105.p1": { "text": "Neutralgrau", "src": "84317097" },
"s5.M.sh106.p1": { "text": "E8E6DF", "src": "573f15a9" },
"s5.M.sh34.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh35.p1": { "text": "F4364C", "src": "689e5837" },
"s5.M.sh107.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh90.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh91.p1": { "text": "C73ECC", "src": "685cf649" },
"s5.M.sh108.p1": { "text": "A", "src": "c40bf6cc" },
"s5.M.sh149.p1": { "text": "© Copyright Microsoft Corporation. Alle Rechte vorbehalten.", "src": "eb8425d5" },
"s5.sh5.p1": { "text": "Microsoft Foundry", "src": "ccb80b4a" },
"s5.sh131.p1": { "text": "Eine einheitliche Plattform für KI-Apps und -Agents", "src": "04c0dca1" },
"s5.sh130.p1": { "text": "Foundry-<i>Ressourcen</i> in Azure bieten Rechenleistung, Speicher, Modellbereitstellung und weitere Dienste", "src": "def6826b" },
"s5.sh130.p2": { "text": "Foundry-<i>Projekte</i> sind isolierte Arbeitsumgebungen für die App- und Agentenentwicklung", "src": "7d9928ca" },
"s5.sh130.p3": { "text": "In einem Projekt arbeiten Sie mit:", "src": "63a267fe" },
"s5.sh130.p4": { "text": "<b>Modellen</b> – wählen Sie Modelle aus einem umfangreichen Modellkatalog aus und stellen Sie sie bereit", "src": "4c53e53a" },
"s5.sh130.p5": { "text": "<b>Agenten</b> – erstellen und verwalten Sie Agenten, die Modelle für Sprachverarbeitung und logisches Denken nutzen", "src": "0ff8d083" },
"s5.sh130.p6": { "text": "<b>Tools</b> – verbinden Sie Agenten mit Tools, damit sie Aktionen ausführen und auf Informationen zugreifen können", "src": "7eac46a1" },
"s5.sh130.p7": { "text": "<b>Wissen</b> – verwalten Sie den Zugriff von Agenten auf Datenquellen, damit sie die Informationen erhalten, die sie zum Handeln benötigen", "src": "f1e79bfc" },
"s5.sh83.p1": { "text": "", "src": "d642d845" },
"s5.sh92.p1": { "text": "Modelle", "src": "01cd0fab" },
"s5.sh95.p1": { "text": "Agenten", "src": "e7af956f" },
"s5.sh97.alt": { "text": "Roboter mit einfarbiger Füllung", "src": "28889807" },
"s5.sh98.alt": { "text": "Roboter mit einfarbiger Füllung", "src": "28889807" },
"s5.sh99.alt": { "text": "Roboter mit einfarbiger Füllung", "src": "28889807" },
"s5.sh102.p1": { "text": "Tools", "src": "950196fc" },
"s5.sh104.alt": { "text": "Schraubenschlüssel mit einfarbiger Füllung", "src": "2fef1b14" },
"s5.sh109.alt": { "text": "Schraubendreher mit einfarbiger Füllung", "src": "85a1be65" },
"s5.sh110.alt": { "text": "Schraubendreher mit einfarbiger Füllung", "src": "85a1be65" },
"s5.sh107.alt": { "text": "Hammer mit einfarbiger Füllung", "src": "9b482cef" },
"s5.sh108.alt": { "text": "Hammer mit einfarbiger Füllung", "src": "9b482cef" },
"s5.sh113.p1": { "text": "Wissen", "src": "ab55c08b" },
"s5.sh115.alt": { "text": "Dokumentumriss", "src": "27286e9a" },
"s5.sh117.alt": { "text": "Lupe mit einfarbiger Füllung", "src": "d243ec84" },
"s5.sh118.alt": { "text": "Lupe mit einfarbiger Füllung", "src": "d243ec84" },
"s5.sh126.p1": { "text": "Microsoft Foundry-<i>Ressource</i>", "src": "cb67db2f" },
"s5.sh127.p1": { "text": "Microsoft Foundry-<i>Projekt</i>", "src": "c5276194" },
"s5.sh3.p1": { "text": "© Copyright Microsoft Corporation. Alle Rechte vorbehalten.", "src": "eb8425d5" },
"s5.notes.p1": { "text": "Definieren Sie Foundry in einem Satz: „Foundry bietet Ihnen eine projektbasierte Arbeitsumgebung, in der Sie KI-Apps und -Agenten mit Modellen, Tools und Wissen an einem zentralen Ort entwickeln können.“", "src": "d82646c3" },
"s5.notes.p2": { "text": "Betonen Sie das zugrunde liegende Konzept: <b>Ressourcen</b> stellen Dienste für Rechenleistung, Speicher und die Bereitstellung von Modellen bereit.", "src": "55564b5c" },
"s5.notes.p3": { "text": "<b>Projekte</b> sind isolierte Arbeitsumgebungen für die App- &amp; Agentenentwicklung.", "src": "8e58c919" },
"s5.notes.p4": { "text": "Innerhalb von Projekten: <b>Modelle</b>, <b>Agenten</b>, <b>Tools</b>, <b>Wissen</b>.", "src": "4d6ec164" },
"s5.notes.p5": { "text": "Praktischer Hinweis: „Wenn die Lernenden den Überblick verlieren, verweisen Sie wieder auf diese Abbildung – die meisten Aufgaben lassen sich einem dieser vier Bereiche zuordnen.“", "src": "54027b8d" }
}
}
Loading
Loading