Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This guide walks you through converting an academic thesis (PhD, HDR, or similar) into a modern Typst project with:

  • Bilingual PDF output – French and English from a single source.
  • HTML website – a responsive, searchable, multi-chapter site.
  • GitHub Pages deployment – automated build and publish on every push.

The techniques were developed while converting two real theses:

ProjectSourceLive site
PhD thesis (PageRank)balouf/phd-pagerankbalouf.github.io/phd-pagerank
HDR thesis (P2P)balouf/hdr-p2pbalouf.github.io/hdr-p2p

Browse them to see what the end result looks like – bilingual toggle, chapter navigation, full-text search, downloadable PDFs.

Modular structure

Each section of this guide is self-contained. Pick the ones you need:

SectionYou need this if…
LaTeX to TypstYour thesis is currently in LaTeX
Bilingual PDFYou want French + English output
HTML ExportYou want a web version of your thesis
GitHub DeploymentYou want automated build + hosting
Working with an LLMYou want to use AI assistants effectively

Already have a Typst project? Skip section 1. Monolingual thesis? Skip section 2. Only need PDF? Skip sections 3 and 4.

Prerequisites

  • Typst >= 0.14.2 – HTML export requires a recent version. Install from typst.app or via cargo install typst-cli.
  • Python >= 3.11 – used by the build script that splits HTML into chapters.
  • uv – the fast Python package runner. Install from docs.astral.sh/uv. The build script uses uv run to manage its own dependencies automatically.
  • Git and GitHub – basic familiarity with repositories, branches, and GitHub Actions.

The boilerplate

The boilerplate/ directory in this repository contains ready-to-use template files:

boilerplate/
  main.typ            # PDF entry point
  main-html.typ       # HTML entry point
  deploy.yml          # GitHub Actions workflow
  templates/
    i18n.typ           # Bilingual support (#t(), lang variable)
    meta-data.typ      # Thesis metadata (title, author, jury)
    html-overrides.typ # Show rules for HTML export
    prelude.typ        # Common imports for chapters
  chapters/
    example.typ        # Example chapter (French)
    example.en.typ     # Example chapter (English)
  web/
    build.py           # HTML build script (split, nav, search)
    assets/            # CSS and JS for the website

Copy the boilerplate into your own repo and follow the TODO: adapt comments in each file. The guide sections below explain the reasoning behind each piece.

Section overview

Section 1 – LaTeX to Typst Strategy and tooling for converting a LaTeX thesis to Typst. When to let an LLM do the heavy lifting and when to review by hand.

Section 2 – Bilingual PDF The i18n.typ architecture: the #t() function, when to use it vs. separate .en.typ files, and the translation workflow.

Section 3 – HTML Export How Typst’s HTML backend works, the show rules in html-overrides.typ, and the build.py script that turns a single HTML blob into a navigable website.

Section 4 – GitHub Deployment Setting up GitHub Actions to compile Typst, run the build script, and deploy to GitHub Pages automatically.

Section 5 – Working with an LLM Practical advice for using large language models as coding partners throughout this workflow – what they excel at and where human judgment is essential.

LaTeX to Typst

This section covers converting a LaTeX thesis to Typst. If your thesis is already in Typst, skip ahead to Bilingual PDF.

When and why convert

There are several good reasons to move from LaTeX to Typst:

  • Bit rot. Old LaTeX sources often stop compiling after a few years – packages vanish, engines change, build toolchains break. Typst is a single binary with no dependency hell.
  • HTML export. Typst >= 0.12 has native HTML output. LaTeX-to-HTML pipelines (tex4ht, LaTeXML, Pandoc) are fragile and rarely produce clean results.
  • Modern tooling. Incremental compilation, built-in package manager, readable error messages, and a scripting language that feels closer to Python than TeX macros.

You do not need to convert if you only need the original PDF. This process is worthwhile when you want to maintain, extend, or republish the thesis.

Strategy: chapter by chapter

Do not try to convert the entire thesis in one pass. Work incrementally:

  1. Create the skeleton. Set up main.typ with page layout, fonts, and imports. The boilerplate’s main.typ is a good starting point. Get it to compile (even if empty).

  2. Convert one chapter at a time. Pick the simplest chapter first to establish patterns. Copy the LaTeX source into a .typ file, convert the markup, compile, and fix errors. Once it looks right, move to the next.

  3. Keep the LaTeX source. Don’t delete the original .tex files until the entire thesis compiles in Typst. You will refer back to them constantly.

  4. Validate the PDF. After each chapter, compare the Typst PDF against the original LaTeX PDF. Check that equations render correctly, figures are placed, and cross-references resolve.

Who does what

The conversion is a collaboration between you, an LLM, and Typst packages.

LLM: bulk markup conversion

Large language models are excellent at the mechanical part of LaTeX-to-Typst conversion. Give the LLM a LaTeX chapter and ask it to produce the Typst equivalent. Be explicit in your instructions:

  • Preserve all \label{} as Typst labels (<label-name>).
  • Preserve all \ref{} / \cite{} as Typst references (@label-name).
  • Keep all math content exactly as-is (only change LaTeX math syntax to Typst).
  • Maintain the same section/subsection structure.

An LLM can typically convert a 20-page chapter in a single pass with ~90% accuracy. The remaining 10% is where human review matters.

Human: review and judgment

After the LLM produces a draft, review carefully:

  • Mathematics. LLMs sometimes subtly alter mathematical notation – swapping a subscript, dropping a superscript, changing a symbol. Compare every equation against the original. This is the highest-risk area.
  • Cross-references. Verify that labels and references compile without warnings. Typst will tell you about unresolved references.
  • Editorial decisions. Some LaTeX constructs have no direct Typst equivalent. You decide how to handle custom environments, unusual layouts, or legacy formatting.

Typst packages

Use established community packages instead of reimplementing LaTeX environments:

LaTeXTypst packageNotes
amsthm / thmtoolsctheoremsTheorem, lemma, definition, proof
algorithm2e / algorithmicxalgoPseudocode algorithms
glossaries / acronymacrosticheAcronym management
subcaption / subfigsubparSubfigures with individual captions

Check the Typst Universe for other packages you may need.

Common pitfalls

Bibliography

Typst supports two bibliography formats:

  • .bib (BibLaTeX/BibTeX) – if you already have a .bib file, just use it directly. This is the path of least resistance.
  • .yml (Hayagriva) – Typst’s native format. Cleaner, but converting from .bib is rarely worth the effort for an existing thesis.

Point to your bibliography file in main.typ:

#bibliography("references.bib", style: "springer-lncs-alphabetical.csl")

CSL style files let you match the citation style of your original thesis. Download the right one from the CSL repository.

Figures

  • PDF figures from LaTeX work directly in Typst. If your LaTeX project generated figures as .pdf files, copy them over and use image("figures/foo.pdf").
  • EPS files need conversion to PDF or SVG first. Use epstopdf or Inkscape for batch conversion.
  • TikZ figures do not exist in Typst. Either keep the compiled PDF output from LaTeX, or redraw with Typst’s drawing primitives or the CeTZ package.

French ligatures (oe, ae)

French text contains ligatures like “œuvre” or “nœud”. LaTeX handles these through font ligatures, but Typst needs the actual Unicode characters.

The boilerplate defines shorthand variables in prelude.typ:

#let oe = "œ"
#let OE = "Œ"
#let ae = "æ"
#let AE = "Æ"

Use them inline with #{} syntax: n#{oe}ud, c#{oe}ur, #{oe}uvre. You can also type the Unicode characters directly (œ, æ) if your keyboard layout supports it.

Why not a global show rule? A blanket show "oe": "œ" would produce false positives: “coefficient” → “cœfficient”, and every English “oe” (does, goes, poem…) would be corrupted. Explicit characters avoid this entirely.

Encoding

Typst is strictly UTF-8. Old LaTeX files may use Latin-1 or other encodings. If you see garbled characters, convert the source file:

iconv -f latin1 -t utf-8 chapter.tex > chapter-utf8.tex

Then feed the UTF-8 version to the LLM for conversion.

Equation numbering

LaTeX and Typst number equations differently. LaTeX typically numbers by section (e.g., Equation 3.2), while Typst numbers sequentially by default.

You can configure Typst’s equation numbering, but don’t waste time trying to reproduce the exact LaTeX numbering. Readers of the new version don’t care about matching the old equation numbers – they care about internal consistency.

Reference projects

Both reference projects started as LaTeX theses and were converted to Typst following this approach:

  • balouf/phd-pagerank – PhD thesis, ~200 pages, heavy on linear algebra and graph theory.
  • balouf/hdr-p2p – HDR thesis, ~80 pages, combinatorics and distributed systems.

Browse their Git history to see the conversion unfold chapter by chapter.

Bilingual PDF

This section explains how to produce French and English PDFs from a single Typst project. If your thesis is monolingual, skip to HTML Export.

Architecture: i18n.typ

The bilingual system lives in templates/i18n.typ (included in the boilerplate). It defines two things:

#let lang = sys.inputs.at("lang", default: "fr")

#let t(fr, en) = if lang == "en" { en } else { fr }
  • lang – read from the command line via --input lang=fr or --input lang=en. Defaults to French.
  • #t(french, english) – the core bilingual function. Pass two values; the one matching the current language is returned.

Compile with:

typst compile --input lang=fr main.typ thesis-fr.pdf
typst compile --input lang=en main.typ thesis-en.pdf

Import lang and t wherever you need them:

#import "templates/i18n.typ": lang, t

When to use #t() vs. separate files

There are two strategies for bilingual content, and you will use both.

Inline #t(): short, mixed content

Use #t() for anything where both languages fit naturally on the same line or in the same block:

// Headings
= #t([Introduction], [Introduction])

// Figure captions
#figure(
  image("figures/graph.pdf"),
  caption: t([Graphe de convergence.], [Convergence graph.]),
)

// Metadata
#let thesis-title = t(
  [#smallcaps[Analyse des réseaux pair-à-pair]],
  [#smallcaps[Analysis of Peer-to-Peer Networks]],
)

// Table of contents
#outline(title: t([Table des matières], [Table of Contents]))

This works well for labels, captions, UI text, and any short bilingual string.

Separate .en.typ files: chapter prose

When a chapter is mostly prose (paragraphs of text with embedded math and references), maintaining both languages interleaved with #t() becomes unreadable. Instead, create a parallel English file:

chapters/
  ch1.typ       # French prose
  ch1.en.typ    # English prose
  ch2.typ
  ch2.en.typ

In main.typ, switch between them:

#if lang == "en" {
  include "chapters/ch1.en.typ"
} else {
  include "chapters/ch1.typ"
}

The key insight: math, labels, figures, and references are the same in both files. Only the surrounding prose changes. Compare the boilerplate’s example.typ and example.en.typ to see this in practice.

Rule of thumb

If more than about 5 consecutive lines need translation, use a separate file. Short labels and captions stay inline with #t().

Translation workflow

Step 1: Write the primary language first

Get the French (or English) chapter fully working – correct math, figures placed, cross-references resolving, no compilation warnings.

Step 2: Let an LLM translate

Give the LLM the complete .typ file and ask for a translation. Be explicit:

  • Translate all prose to English (or French).
  • Preserve all Typst markup exactly: #import, labels (<...>), references (@...), math ($...$), function calls.
  • Do not rename labels or references.
  • Preserve the #import line at the top of the file.

An LLM can translate a 15-page chapter in seconds. The output is usually syntactically correct on the first try.

Step 3: Human review

After the LLM produces the translation:

  • Specialized terminology. Academic fields have precise vocabulary. Verify that the LLM used the correct English (or French) terms for your domain.
  • Mathematical prose. Sentences like “Let $f$ be a continuous function on $[0,1]$” need to read naturally in both languages. The LLM usually handles this well, but check.
  • Cultural references. French academic conventions (e.g., “on montre que”) don’t always translate literally.

Step 4: Compile and compare

typst compile --input lang=fr main.typ thesis-fr.pdf
typst compile --input lang=en main.typ thesis-en.pdf

Open both PDFs side by side. Check that all figures, equations, and references appear in both versions.

Pitfall: smart quotes around math in HTML

This issue is specific to HTML export but surfaces during bilingual work. Typst’s smart quote algorithm can misidentify the direction of quotation marks when they appear adjacent to math ($...$). For example:

the "value of $x$"   // may produce wrong quote direction in HTML

The fix is to use explicit Unicode quote characters:

LanguageOpeningClosingUnicode
English\u201C\u201D\u{201C} / \u{201D}
French\u00AB\u00BB\u{AB} / \u{BB}

In practice, this only matters for the handful of places where quotes sit next to math. Most prose is unaffected.

HTML Export

This chapter covers the full pipeline from Typst source to a navigable, multi-page HTML website. The key idea: Typst compiles your thesis into a single HTML blob, then a Python script splits it into one page per chapter with sidebar navigation, search, and cross-chapter links.

Dual Entry Points

Your project has two Typst entry points:

  • main.typ — PDF output. Includes page setup, headers/footers, table of contents, page numbers.
  • main-html.typ — HTML output. Wraps each chapter in a chapter-section() call so build.py knows where to split. Applies HTML-specific show rules.

Both files import the same chapter files (chapters/*.typ), so the content is shared. The difference is in the structural markup around them.

Compiling for HTML:

typst compile --features html --format html \
  --input lang=fr --input html=true \
  main-html.typ web/dist/full-fr.html

The --features html flag unlocks the target() function, and --input html=true sets the _is-html flag that chapter files can read without needing --features html themselves.

html-overrides.typ

This file (in templates/) provides three things:

chapter-section(id, body)

Wraps content in <section class="chapter" id="id"> for HTML; passes through transparently for PDF. This is the marker that build.py uses to find and split chapters:

#chapter-section("distribution")[
  #include "chapters/distribution.typ"
]

The id must match the section_id in build.py’s CHAPTERS list.

part-marker(id, title)

Optional grouping headers. In HTML, emits a <section class="part"> with an <h1> that appears in the sidebar as a part heading (e.g., “Part I: Content Distribution”). In PDF, it does nothing — part pages are handled by the thesis style.

html-show-rules

A set of show rules applied only in HTML mode, using target():

  • Headings are converted to semantic <h1><h4> with id attributes derived from their labels. Chapter counters are reset at each <h1>.
  • Inline math is rendered as SVG via html.frame() wrapped in a box (so it stays inline).
  • Block math is rendered as SVG via html.frame().
  • Grids (e.g., side-by-side figures) are rendered as SVG with constrained width (42em) to avoid oversized output.
  • Algorithms (figures of kind "algorithm") are rendered as SVG at 38em width to force line wrapping within the HTML content area.

Important caveat about target(): The target() function is only available when compiling with --features html. Files included by both main.typ and main-html.typ (i.e., your chapter files and shared templates) cannot call target() directly — it will error during PDF compilation. Instead, use the _is-html flag from i18n.typ:

#import "i18n.typ": _is-html

#let my-function = if _is-html {
  // HTML version
} else {
  // PDF version
}

Only html-overrides.typ itself (which is imported exclusively by main-html.typ) may use target().

Theorem Environments

The file environments.typ provides dual implementations for every theorem type (theorem, lemma, proposition, corollary, definition, conjecture, remark, etc.):

  • PDF mode: uses the ctheorems package (thmbox) with colored fills and borders.
  • HTML mode: uses _html-thm() to generate <div class="thm-box thm-theoreme"> wrapped in a figure (for labeling and numbering).

Each environment type has a CSS class (e.g., .thm-theoreme, .thm-lemme, .thm-proposition). The css-class argument in _html-thm() must match a rule in style.css.

Common gotcha: if you add a new theorem type and forget the CSS rule, the box will render with no background and no border — effectively invisible. Always add both the light and dark theme CSS rules:

.thm-mytype    { background: #f0f0f0; border-color: #999; }

[data-theme="dark"] .thm-mytype { background: #1e1e1e; }

build.py — The Build Script

The Python build script (web/build.py) orchestrates the full pipeline. Run it with:

uv run web/build.py                 # Build both languages
uv run web/build.py --skip-lang en  # French only
uv run web/build.py --skip-pdf      # Skip PDF compilation
uv run web/build.py --skip-compile  # Reuse existing full.html (faster iteration)

The script performs these steps:

  1. Compile Typst to HTML — runs typst compile --features html --format html for each language, producing a single full-fr.html / full-en.html.
  2. Parse and split — loads the HTML with BeautifulSoup, finds all <section class="chapter"> elements, and maps their id to the chapter list.
  3. Rewrite cross-chapter links — any href="#some-id" pointing to an element in a different chapter is rewritten to href="other-file.html#some-id".
  4. Collect footnotes — Typst emits all footnotes as endnotes in a single section. The script distributes them back to their respective chapter pages.
  5. Generate pages — each chapter becomes a standalone HTML page with: a sticky topbar, a left sidebar (global navigation), a right sidebar (local table of contents from h2/h3 headings), and prev/next navigation links.
  6. Generate redirect indexdist/index.html detects the browser language and redirects to fr/ or en/.
  7. Copy CSS/JS assets — copies style.css, nav.js, and any other files from web/assets/ into dist/assets/.
  8. Run Pagefind — indexes the generated pages for client-side full-text search.

Configuration in build.py

The top of build.py has a configuration section marked with # TODO: adapt comments. Here is what to customize:

CHAPTERS

A dict keyed by language, each value a list of (section_id, filename, title) tuples:

CHAPTERS = {
    "fr": [
        ("cover", "index.html", "Accueil"),
        ("distribution", "distribution.html", "Distribution de contenu"),
        ("bibliography", "bibliography.html", "Bibliographie"),
    ],
    "en": [
        ("cover", "index.html", "Home"),
        ("distribution", "distribution.html", "Content Distribution"),
        ("bibliography", "bibliography.html", "Bibliography"),
    ],
}

The section_id must match the id in the corresponding chapter-section("id") call in main-html.typ. If they do not match, the script will print a warning and skip the chapter.

PARTS

Groups chapters in the sidebar. Each entry is (title_or_none, [list_of_section_ids]):

PARTS = {
    "fr": [
        (None, ["cover", "introduction"]),
        ("Distribution", ["distribution", "caching"]),
        (None, ["bibliography"]),
    ],
}

Use None for a flat group (no heading), or a string for a labeled group that renders as a part header in the sidebar.

SUB_CHAPTERS

A set of section_id values that should appear indented and in a smaller font in the sidebar:

SUB_CHAPTERS = {"acyclique-origine", "acyclique-bases", "acyclique-convergence"}

This is useful when decomposing a large chapter into multiple pages (see below).

Other settings

  • THESIS_TITLE — the title shown in the topbar, per language.
  • GITHUB_URL — links to the GitHub icon in the topbar.
  • BASE_URL — overridden by the --base-url CLI argument; critical for GitHub Pages deployment.

CSS Theming

The file web/assets/style.css uses CSS variables for light and dark modes. The root :root block defines light theme colors, and [data-theme="dark"] overrides them.

Key conventions:

  • .thm-* classes define theorem box appearance (background + left border).
  • SVG frames from html.frame() get the class .typst-frame. In dark mode, they are inverted with filter: invert(1) hue-rotate(180deg) to flip black-on-white to white-on-dark while preserving colors.
  • The layout is a CSS grid with three columns (left sidebar, content, right TOC) that collapses responsively at 1200px and 960px breakpoints.

The file web/assets/nav.js handles theme toggling (light/dark/auto cycle), sidebar open/close on mobile, Pagefind search modal (lazy-loaded on first open), keyboard shortcuts (Ctrl+K for search, Escape to close), and scroll-spy highlighting in the local table of contents.

Decomposing a Large Chapter

If a chapter has natural sub-sections that would each make a long page (like the acyclic preferences chapter in hdr-p2p), you can split it into multiple HTML pages while keeping the single chapter structure in the PDF.

In main-html.typ, use separate chapter-section() calls for each sub-section:

#chapter-section("acyclique")[
  #include "chapters/acyclique-html-intro.typ"
]
#chapter-section("acyclique-origine")[
  #include "chapters/acyclique-origine.typ"
]
#chapter-section("acyclique-bases")[
  #include "chapters/acyclique-bases.typ"
]

In main.typ, include the full chapter as a single unit (the PDF has no splitting).

Then in build.py, list the sub-sections in CHAPTERS and add their IDs to SUB_CHAPTERS so they render indented in the sidebar. The parent entry ("acyclique") appears as a top-level item, and the sub-entries appear nested below it.

This gives readers a manageable page length in the browser while the PDF retains its natural flow.

GitHub Deployment

This chapter covers publishing your thesis website to GitHub Pages with automated builds.

Repository Structure

A typical repository layout:

your-thesis/
├── .github/workflows/deploy.yml
├── .gitignore
├── README.md
├── main.typ              # PDF entry point
├── main-html.typ         # HTML entry point
├── templates/            # Shared Typst templates
├── chapters/             # Chapter source files (.typ)
├── figures/              # Images and diagrams
├── web/
│   ├── build.py          # Build script
│   └── assets/
│       ├── style.css
│       ├── nav.js
│       └── favicon.svg
├── references.bib
└── springer-lncs-alphabetical.csl

.gitignore

Essential entries to keep the repository clean:

# Build output
web/dist/

# PDFs (except figure PDFs)
*.pdf
!figures/**/*.pdf

# Typst cache
.typst-cache/

# Python
__pycache__/
.venv/

# OS
.DS_Store
Thumbs.db

deploy.yml

The GitHub Actions workflow (provided in boilerplate/deploy.yml) automates the full build-and-deploy cycle. Key points:

  • Triggers: push to main and manual dispatch (workflow_dispatch).
  • Permissions: needs pages: write and id-token: write for GitHub Pages deployment.
  • Steps: checkout, install Typst (typst-community/setup-typst@v4), install uv (astral-sh/setup-uv@v5), run build.py, upload artifact, deploy to Pages.

Three settings deserve attention:

--base-url is critical. GitHub Pages serves your site at https://username.github.io/repo-name/, so all asset paths (CSS, JS, Pagefind index) must be prefixed with /repo-name/. The build command in the workflow should read:

run: uv run web/build.py --base-url /your-repo-name/

If you forget this, the site will load with no styles and broken search.

FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true is set as an environment variable to suppress Node.js deprecation warnings from older GitHub Actions.

enable-cache: false on setup-uv is necessary because build.py uses inline script dependencies (the # /// script header) rather than a uv.lock file. With caching enabled, uv would look for a lock file and fail.

Enabling GitHub Pages

Before the first deployment:

  1. Go to your repository on GitHub.
  2. Navigate to Settings > Pages.
  3. Under Source, select GitHub Actions.

That is all. The workflow will handle the rest on the next push to main.

Favicon

Create a simple SVG favicon in web/assets/. The <link rel="icon"> tag is generated by build_page() in build.py, pointing to ../assets/favicon.svg.

A minimal SVG with colored background and text initials works well:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <rect width="32" height="32" rx="4" fill="#2196F3"/>
  <text x="16" y="22" text-anchor="middle"
        font-family="sans-serif" font-size="16" font-weight="bold"
        fill="white">FM</text>
</svg>

Replace the initials and color to match your identity.

README.md

Include at minimum:

  • A “Read online” link: https://username.github.io/repo-name/
  • Build instructions for local development:
    uv run web/build.py --base-url /
    open web/dist/index.html
    
  • PDF compilation instructions:
    typst compile --input lang=fr main.typ thesis-fr.pdf
    typst compile --input lang=en main.typ thesis-en.pdf
    
  • License information (your thesis content license).

Deployment Checklist

When setting up a new thesis repository:

  1. Copy the boilerplate files into your project.
  2. Edit build.py: set CHAPTERS, PARTS, SUB_CHAPTERS, THESIS_TITLE, and GITHUB_URL.
  3. Edit deploy.yml: change --base-url /your-repo-name/.
  4. Create your favicon.svg.
  5. Push to main and verify the Actions tab shows a successful build.
  6. Visit https://username.github.io/your-repo-name/ to confirm the site is live.

The reference projects phd-pagerank and hdr-p2p both follow this exact structure and can serve as working examples.

Working with an LLM

Both reference projects (phd-pagerank and hdr-p2p) were built with heavy LLM assistance. This chapter shares what worked, what did not, and how to structure the collaboration effectively.

What LLMs Excel At

LaTeX to Typst conversion

Give an LLM a LaTeX chapter and ask for clean Typst output. This works remarkably well for standard academic markup: sections, theorems, equations, figures, tables, citations, cross-references. A 30-page chapter typically converts in one pass with only minor fixes needed (usually around label formatting or package-specific commands).

Translation

Academic prose translates very well. The mathematical content stays unchanged (equations, labels, references are language-neutral), so the LLM only needs to handle the surrounding text. For a bilingual thesis, translating a French chapter to English (or vice versa) is one of the highest-value uses of an LLM.

Boilerplate generation

CSS stylesheets, build scripts, GitHub Actions workflows, README files — all the infrastructure around the thesis content. These follow well-known patterns and an LLM can produce working versions quickly. The entire style.css, nav.js, build.py, and deploy.yml in the boilerplate were LLM-generated with iterative refinement.

Adapting existing patterns

“Make this file follow the same pattern as that file” is a prompt that works consistently. If you have one chapter converted and want to convert the next one in the same style, give the LLM both the source and the example output. It will replicate conventions (heading levels, label naming, theorem usage) accurately.

What Humans Must Do

Review all mathematical content

LLMs can subtly alter notation, drop indices, change quantifier scope, or rephrase a condition in a way that changes its meaning. Every equation, theorem statement, and proof step needs human verification. This is non-negotiable.

Make editorial decisions

Which chapters to include, how to structure the HTML navigation, what to translate versus keep in the original language, whether to split a long chapter into sub-pages — these are judgment calls that define the final product.

Verify cross-references

After conversion or translation, compile immediately. Broken labels (@some-label that no longer exists) will show as errors. Cross-references between chapters are especially fragile during restructuring.

Check specialized terminology

Domain-specific terms may not translate correctly. A “stable matching” in game theory is not the same as a “matching stable” in French (the adjective placement matters). Review translated terminology against the conventions of your field.

Final visual review

Both the PDF and the HTML output need a visual pass. Check that figures render correctly, theorem boxes have the right colors, math is not clipped, and the sidebar navigation makes sense.

Prompting Tips

Provide full context

Always give the LLM both the source file content and the target pattern or conventions. Do not rely on it guessing your project structure.

Be explicit about preservation

For conversion tasks, use prompts like: “Convert this LaTeX to Typst. Preserve ALL labels (@xxx), references, math ($...$), footnotes, and heading structure. Do not summarize or rephrase any content.”

For translation, be surgical

“Translate ONLY the French prose to English. Keep all Typst markup, labels, references, math environments, and code exactly as they are. Do not translate proper nouns or established technical terms.”

One file at a time

Process files individually for better quality. Bulk processing (multiple chapters in one prompt) leads to more errors, truncation, and inconsistencies. The overhead of separate prompts is small compared to the debugging cost.

Use parallel agents for independent work

If you have six chapters to translate, you can run six parallel agent sessions — one per chapter. They are independent tasks with no shared state. This is where LLMs save the most wall-clock time.

Iterative Workflow

The most effective process is a tight loop:

  1. Convert or translate a single file with the LLM.
  2. Compile immediately (typst compile main.typ or the HTML pipeline).
  3. Fix any errors — broken references, missing imports, syntax issues.
  4. Visual check — open the PDF or HTML and scan for rendering problems.
  5. Commit the working state.
  6. Repeat for the next file.

This catches problems early. Do not batch too many LLM-generated changes before compiling — if you convert five chapters and then try to compile, debugging the accumulated errors is painful.

For the HTML pipeline specifically, a useful shortcut is:

uv run web/build.py --skip-pdf --skip-lang en --base-url /

This builds only the French HTML (skipping English and PDF), which is fast enough for iterative checking.

Cost-Effective Split

The most efficient workflow is:

  • LLM for the 90% mechanical work: LaTeX-to-Typst conversion, prose translation, CSS/JS boilerplate, build script generation, README writing.
  • Human for the 10% that requires judgment: mathematical review, editorial decisions, terminology verification, visual checks, project structure.

This matches how the two reference projects were actually built. The LLM produced the first draft of nearly every file; the human reviewed, corrected, and made structural decisions. The total effort was a fraction of doing everything manually, with the human time focused where it matters most.