Devlog #82 – Polish Localization Phase 1: 89 New Pages, 3-Way Language Switcher & a 2247-URL Sitemap

MySimulator has been bilingual (EN + UK) since v1.0. With v1.1.8 we quietly added a third locale: PL. The payload is 89 fresh Polish pages — homepage with FAQPage rich-snippet markup, a sortable categories hub, and 80 category landings — plus curated titlePl + descriptionPl translations baked into the data layer, a 3-way navbar cycling EN → UK → PL, and hreflang="pl" wired into 174 existing pages. The sitemap grew from 2158 to 2247 URLs.

v1.1.8 — PL Phase 1 (infrastructure release)
89
New PL pages
80
Categories translated
174
Hreflang updates
2247
Sitemap URLs

Why Polish, and why now?

The site has a sizeable Polish audience reading the English version (Google Search Console shows pl-PL as the #5 country by impressions), and Polish scientific terminology has well-established academic conventions that machine translation tends to mangle ("Inżynieria Materiałowa", not "Inżynieria Materiałów"; "Dynamika Płynów", not "Dynamika Cieczy"). Doing this as a single infrastructure release rather than rolling translations into weekly waves keeps the locale layer surgically clean.

What "Phase 1" actually covers

Phase 1 is the entry-point layer — every page a Polish reader is likely to land on from organic search:

Phase 1 deliberately does not translate the 559 simulation pages or 295 articles. Those would be a Phase 2 effort (Q3 2026 plan) because the value-per-page of translating an entry-point ("Fizyka kwantowa") is much higher than translating one specific niche sim ("BB84 protocol").

The translation layer

Translations live in the data, not in templates. Each row in shared/data/categories.json now has two extra fields next to its UK counterparts:

{
  "id": "fluid-dynamics",
  "title": "Fluid Dynamics",
  "titleUk": "Гідродинаміка",
  "titlePl": "Dynamika Płynów",
  "description": "SPH, MPM, Navier-Stokes equations...",
  "descriptionUk": "SPH, MPM, рівняння Нав'є-Стокса...",
  "descriptionPl": "SPH, MPM, równania Naviera-Stokesa, fale strzemienne, turbulencja."
}

Translations are curated, not machine-generated. A small sample of decisions that mattered:

Category IDEnglishPolish (chosen)Why not the literal MT
materials-scienceMaterials ScienceInżynieria MateriałowaPolish academia uses the engineering framing for this field; "Materiałoznawstwo" exists but is dated.
fluid-dynamicsFluid DynamicsDynamika Płynów"Płyny" covers both liquids and gases; "Cieczy" would exclude gases.
chronobiologyChronobiologyChronobiologiaDirect loanword; "Biologia rytmów" would obscure the field name.
networksNetworksSieciBare "Sieci", not "Sieci komputerowe", because the category covers social/biological/transport networks too.
perceptionPerceptionPercepcjaBoth "Percepcja" and "Postrzeganie" work; "Percepcja" is the term in cognitive science textbooks.
game-mathGame MathMatematyka GierGenitive case ("Matematyka Gier") is the natural compound, not "Matematyka Gra".

3-way navbar switcher

Before v1.1.8 the language switcher in the navbar was a single button that toggled EN ↔ UK based on the current path. Adding a third locale forced a design choice: dropdown, three pills, or cycle? The cycle won — same single button, but now goes EN → UK → PL → EN on each click. Two reasons:

The cycle is biased towards EN because that's the most-complete locale — if you cycle past your target, one more click brings you to EN, which always has the page.

// shared/components.js
function getLangHrefs() {
  const rootUrl = new URL(_base || "./", window.location.href).href;
  const noLang = currRelPath.replace(/^(uk|pl)\//, "");
  const hrefs = {
    en: rootUrl + noLang,
    uk: rootUrl + "uk/" + noLang,
    pl: rootUrl + "pl/" + noLang,
  };
  const cycle = { en: "uk", uk: "pl", pl: "en" };
  hrefs.next = hrefs[cycle[_lang] || "uk"];
  return hrefs;
}

Hreflang surgery on 174 existing pages

Adding a third locale isn't just about creating new files; every existing EN and UK page that has a PL twin needs a <link rel="alternate" hreflang="pl"> in its head, otherwise Google won't connect the cluster. The add_pl_hreflang.js script:

  1. Loops over root index, UK index, the categories hub, all 80 EN + 80 UK categories, and 14 static-page pairs.
  2. Detects existing hreflang="pl" tags and skips if already correct.
  3. Strips any wrong PL tags (e.g. pointing to the wrong target).
  4. Inserts the new tag immediately after hreflang="uk" if present, falling back to after hreflang="en", falling back to before </head>.

Idempotent by construction: running it twice changes zero files. 174 pages updated, 0 already correct (first run), 4 files missing (those were UK twins that never existed for some static pages).

Sitemap: 2158 → 2247 URLs

scripts/gen_sitemap.js picked up PL automatically because the existing per-locale loop now checks for pl/<path>/index.html in addition to the EN and UK variants. Each URL entry has a <xhtml:link rel="alternate" hreflang="pl"> sibling when the PL twin exists.

PL URLs are emitted at priority 0.05–0.10 below their EN counterparts (so 0.8 for PL category vs 0.9 EN) as a soft signal that the locale is a Phase 1 stub. The five sub-sitemaps now total 2247 URLs: sitemap_categories.xml (240, +80), sitemap_sims.xml (1118), sitemap_articles.xml (555), sitemap_blog.xml (224), sitemap_static.xml (28, +6).

FAQPage as a search-result accelerator

Polish-language FAQPage rich snippets remain relatively underused on scientific-education queries, which makes them a good early-mover bet for Phase 1. The PL homepage has 8 FAQ entries covering the questions that come up in support email and Discord: "Czy trzeba coś instalować?", "Czy te symulacje są naukowo poprawne?", "Czy mogę używać symulacji w szkole?". Each answer is 100–300 words to clear Google's quality threshold; collectively they're the most-readable single document about what MySimulator is for, in any language.

What's deferred to Phase 2 (Q3 2026)

Order of impact (estimated from EN→UK Phase 1→2 conversion ratios): sim pages give 5–10× the click volume of category pages, articles give 2–3× (longer dwell, fewer bounces), and tag pages fill in the long tail.

Files touched

Five new scripts were enough to drive the entire release; everything else is generated:

Running node scripts/add_pl_translations.js && node scripts/gen_pl_pages.js && node scripts/add_pl_hreflang.js && node scripts/gen_sitemap.js reproduces the entire release from the data layer alone.

Up next

Wave 62 returns to simulations next week — likely fluid dynamics and statistical mechanics. The PL Phase 2 prep (search-index update, sim translation tooling) will happen quietly in parallel; expect Phase 2 to land around the Q3 2026 boundary.

← Devlog #81: Wave 61 All posts →