Hashi
Multilingual WordPress demo for a fictional cross-border design consultancy (Tokyo / Mexico City / Berlin) — EN / ES / JA via Polylang free, locale-symmetric URLs with genuine Unicode slugs (`/ja/サービス/`), hreflang on every translated page, conditional CJK font loading, custom server-rendered language-switcher block.

Overview
A multilingual WordPress portfolio for a fictional cross-border boutique design consultancy with offices in Tokyo, Mexico City, and Berlin — surfaced in English, Spanish, and Japanese. Built to argue against the "WordPress can't really do multilingual without WPML's $200+/yr license" reflex. The standard stack — Polylang free + a per-demo theme + one new server-rendered block — delivers locale-symmetric URLs, real hreflang, a working <html lang="..."> attribute per locale, and conditional CJK font loading.
24 page rows linked into 8 translation groups:
- 5 main pages × 3 languages = 15 rows: Home, Studio, Practice (Servicios / サービス), Work (Trabajo / 事例), Contact (Contacto / お問い合わせ)
- 3 case studies × 3 languages = 9 rows: Mexico City coffee chain rebrand, Tokyo Modern Art Museum wayfinding, Berlin bicycle manufacturer brand
- All 24 rows linked via
pll_save_post_translations()so the language switcher routes per-page rather than to language homes
Locale-symmetric URLs with genuine Unicode slugs:
/en/practice/ ↔ /es/servicios/ ↔ /ja/サービス/. Japanese slugs are real Unicode — the address bar shows the characters, not percent-escapes. Polylang configured with hide_default=0 so EN is always prefixed (symmetric URLs are clearer than asymmetric /practice/ vs /es/servicios/).
The custom block — sfp-blocks/language-switcher:
Server-rendered. Reads pll_the_languages() and pll_get_post() for each language's translation of the currently queried object. Lists the current page's translations rather than a generic flag bar — click ES from a Tokyo case study and you land on the Spanish translation of that case study, not the language home. Untranslated languages render as aria-disabled with a tooltip ("Not yet translated"). Four attributes: displayStyle (native name vs ISO code), separator, showCurrentAs (underline / bold / dim), fallbackBehavior.
CJK + Latin typography harmonised via the Noto family:
Noto Serif Display + Inter for Latin headings + body. Noto Serif JP + Noto Sans JP for CJK. All from the same superfamily so x-heights and stroke weights align across scripts. Type scale differs by locale because Japanese characters at the same px size read optically larger — Latin display at 4rem, JA display at 3.5rem, JA line-height 1.75 vs Latin 1.65.
Conditional CJK font loading:
functions.php reads pll_current_language() and only enqueues the CJK fonts + a CJK-only stylesheet when the current language is ja. Latin-only visitors never download the ~280kb of CJK fonts. Verified via DevTools Network tab.
Polylang configured via bootstrap, not runtime:
First version tried to enforce the polylang option on every pageload via a plugins_loaded mu-plugin. Polylang's options class normalises boolean values on save, and the runtime updates fought with that normalisation — values kept reverting. Resolution: a one-shot bootstrap-polylang.php runs once via wp eval-file after Polylang activation. Loads PLL_Admin_Model directly (the public API doesn't expose add_language() in CLI / frontend context), registers en/es/ja, writes the option once. The mu-plugin now only exposes the translation linker helper used by the seed.
The Challenge
Clients with international audiences are told that WordPress can't do real multilingual without WPML's recurring license, and that even with WPML the URLs end up ugly and the SEO is half-broken. This perception sends a lot of multi-market projects to Webflow or to a per-locale subdomain hack that wastes editorial energy maintaining parallel sites.
The Solution
Polylang free configured for symmetric subdirectory URLs (/en/, /es/, /ja/) with locale-appropriate Unicode slugs. One server-rendered switcher block that lists this page's translations. The Noto superfamily for CJK + Latin harmony with conditional font loading. The standard WordPress stack — zero plugin cost above core — produces a credibly multilingual site with real hreflang and a UX that doesn't strand readers in the wrong language.
Results
Lighthouse 67 / 95 / 100 / 92 (perf / a11y / best-practices / SEO) — perf is the cost of 19 large source PNGs at native resolution; a pre-processing pass to WebP at 1920px max width would lift perf into the high 80s without touching design
URL strategy:
/serves the EN home directly;/es/and/ja/301 to/es/home-es/and/ja/home-ja/. Inner pages (/es/servicios/,/ja/サービス/, every case study) route cleanly without redirects. Polylang Pro per-languagepage_on_frontwould eliminate the one-hop on the non-default language homes.24 page rows live across EN / ES / JA, linked into 8 Polylang translation groups
Locale-symmetric URLs with genuine Unicode slugs —
/ja/事例/メキシコシティ-カフェチェーン-リブランディング/displays correctly in the address barhreflang on every translated URL +
<link rel="alternate" hreflang="x-default">pointing at EN canonicalCJK font subset (~280kb) loads only on
/ja/pages — verified via DevToolsNew
sfp-blocks/language-switcherblock joins the platform — available to any future multilingual demoi18n pattern (locale-symmetric slugs + conditional CJK enqueue + bootstrap-Polylang script) now reusable
Gallery


