DEV Community

NICK
NICK

Posted on

Two language axes, one static build

Two language axes, one static build

Most multilingual sites have one language axis: the page is in English or in Chinese. That model breaks the moment your subject is language.

If I am an English speaker reading about Spanish /θ/, and a Chinese speaker reads about the same Spanish /θ/, we want the same numbers and different interface text. Neither of us wants a machine translation of the other's page. So the site needs two axes:

  • interface language — the chrome: navigation, captions, explanations
  • target language — the subject: which phoneme inventory is being described

That is 2 × 3 combinations today (English and Chinese interface; English, Spanish and Japanese subject), and the current build serves 166 URLs:

/en/english/…   33 pages     /zh/english/…   33
/en/spanish/…   28 pages     /zh/spanish/…   28
/en/japanese/…  17 pages     /zh/japanese/…  17
+ 10 meta pages (about, contact, privacy, sitemap, roots)
Enter fullscreen mode Exit fullscreen mode

Doubling the interface language doubles the page count without duplicating a single measurement. The data is shared; only the shell differs.

The trap is treating it as six translations

The obvious implementation is six sets of pages: en-english, en-spanish, en-japanese, zh-english, and so on. That produces six content pipelines and six places for a number to drift.

What actually keeps this manageable is separating the layers:

  1. Measurement — one build step computes every figure from the audio. Language-agnostic. Runs once per build.
  2. Content — one record per phoneme per target language, holding the measured values plus any language-specific prose about how that sound behaves.
  3. Shell — one string table per interface language: navigation, table headers, captions, the "measured tokens" label, the empty-cell note.

The renderer combines (2) and (3). Adding a fourth target language means adding records, not rebuilding the site. Adding a third interface language means translating a string table.

The measured numbers never pass through a translation layer, which is the point: a number is not a word, and a translation step is where a number quietly becomes a different number.

Empty is a value, and it has to survive the shell

Every page renders a table. Some cells have no value, because the audio could not support the measurement. Those cells are not zero, and they are not blank-by-accident — they are a deliberate absence, and the interface has to say so in the reader's language.

This is a small thing that becomes large in a two-axis design. The empty-cell note is shell text, so it exists twice; the absence it describes is content, so it exists three times. If the shell says "not measured" in one language and "0" renders as a fallback in the other, the same page tells two different stories about the same data.

The rule I settled on: absence is rendered as absence in every shell, and the note explaining it is part of the shell's contract, not a per-page afterthought.

Switching axes must not lose the reader

If you are reading about Spanish /θ/ and switch the interface to Chinese, you should land on the Chinese shell for Spanish /θ/ — not on a homepage, and not on the Chinese page about English.

That means the URL has to carry both axes in a fixed position (/{interface}/{target}/{slug}), and the language switch has to be a path rewrite rather than a redirect to an index. Getting this wrong is the most common defect in two-axis sites: the switch works on the homepage and quietly breaks four levels deep, where it matters most.

The flip side is that both axes have to be discoverable from every page, which doubles the navigation surface. There is no clever way around it: two independent choices need two independent controls, visible at once.

What static buys here

Every page is a file. The whole site is regenerated on each build: measurement, then content, then shells, then the 166 HTML documents. There is no server deciding which language to render, no cache key to invalidate, no request-time translation.

The cost is that a full build touches everything: fixing one number means regenerating every page it appears on, rather than editing a row in a database. That is a fair trade for a site whose value is the numbers being identical everywhere they appear.


The current build is at phonemetrick.net — 166 pages, two interface languages, three phoneme inventories, and no database.

Top comments (0)