Skip to content
dirltrscriptLatinlh1.7
2025
  • React Context
  • TypeScript
  • CSS Custom Properties
  • i18n

Multi-Language Typography Engine

Typography that doesn't just translate text — it reconfigures the entire reading surface for each language's structural requirements.

Standard i18n implementations swap text strings and stop there. The actual problem is deeper: Arabic and Hebrew require RTL layout direction, CJK scripts require fundamentally different font stacks and line-height adjustments, and some languages have UI strings long enough to break fixed-width navigation elements. A typography engine that only handles string replacement produces a site that technically supports eleven languages but visually reads like it was designed for one.

Language switching is implemented as a coordinated state update that modifies the document's dir attribute, the html element's lang, the body's font-family via a scoped CSS class, and all UI strings simultaneously — from a single context dispatch.

  • localStorage persistence: the selected language is stored in localStorage and read on initial hydration to prevent flash-of-wrong-direction on return visits. The SSR default is 'en' with LTR; the client immediately reconciles on mount.
  • CJK font stack: Japanese and Mandarin apply a separate --font-body token resolving to system CJK fonts. Line height increases from 1.6 to 1.8 for CJK to accommodate taller character bounding boxes.
  • RTL cascade: Arabic and Hebrew set dir='rtl' on the html element, which reverses flex direction across the entire layout. Directional values are written using CSS logical properties (padding-inline-start, not padding-left) throughout.
  • Dynamic dir management: The LanguageSwitcher imperatively updates document.dir on selection. This is intentionally imperative rather than declarative to avoid an intermediate render with the wrong direction.

The font switching mechanism relies on data-lang attributes cascaded from the html element rather than JavaScript-injected inline styles. Each language has a CSS rule matching [data-lang='ja'] body { font-family: var(--font-cjk); line-height: 1.8; } — which means the font change happens in a single style recalculation pass rather than through multiple JS DOM mutations. The LanguageSwitcher component itself is dynamically imported with ssr:false, preventing hydration mismatches from the localStorage read.

Eleven languages rendering correctly with appropriate typography, reading direction, and UI strings — loaded from a single shared stylesheet with no per-language CSS bundles.

  • 11 languages: Latin, RTL (Arabic, Hebrew), CJK (Japanese, Mandarin), Cyrillic, Nordic
  • Single CSS bundle with data-attribute language scoping
  • No layout shift on language switch
  • localStorage persistence eliminates repeat-visit FOUC