K KodePressdocumentation
Laravel 11 · MySQL 8 Specification Built 2026-10-10

12 — Internationalisation#

Two concerns, kept separate:

  1. Admin UI language — what the staff see. Per user (users.locale), Bengali by default.
  2. Public site languages — what visitors see. Per site (sites.locales), with content per language.

12.1 Admin UI#

Writing style for strings#

Plain, short, and the same register in both languages. Buttons are verbs (Publish, প্রকাশ করুন). Errors say what to do next, not what the system failed to do. No developer vocabulary in any owner-facing string — not "payload", "validation failed", "null", "cache", "JSON". Where a technical word is unavoidable (SSL, cron), the string explains it in half a sentence.

12.2 Public site languages#

sites.locales lists the active public languages, sites.default_locale names the primary one. A single-language site keeps ["bn"] and nothing about URLs or switchers appears anywhere.

URL strategy#

Case URL
Single language /about — no prefix, ever
Multi-language, default locale /about (default is unprefixed) or /bn/about if prefix_default is on
Multi-language, other locale /en/about
Homepage / and /en

Decided in ADR-0005. The prefix is a path segment, not a subdomain and not a query parameter: it is cacheable per path, obvious to the owner, and does not need DNS work on shared hosting.

SetLocale middleware resolves in this order: an explicit URL prefix, then a kp_locale cookie (only for the bare /), then sites.default_locale. The browser Accept-Language header is not used for the first request, because a cached HTML file cannot vary by header without splitting the cache per language per visitor.

Translated content#

pages.locale holds the language of the row. pages.translation_of groups translations: the first page created is the group root and translations point at its id (the root points at itself or null).

12.3 Bengali typography#

Concern Rule
Fonts Noto Sans Bengali (body) and Noto Serif Bengali (headings), self-hosted woff2, bundled
unicode-range Bengali block split from Latin so Latin text does not pull the Bengali file
Subsetting by Unicode range only. Never by observed glyphs — Bengali conjuncts would break
Line height default 1.7 for Bengali body text (Bengali needs more leading than Latin); the token default reflects this
Font size base 16 px; Bengali at the same optical size reads smaller, so the bn locale adds 1.0625rem body size via a locale class on <html>
Numerals site option numerals: bengali | latin, applied by the shared formatting helper for dates and counts
Dates all dates render through fmt_date() / fmt_datetime(), which take the site timezone, the site date format and the locale — including Bengali month names. No date() or ->format() anywhere in a view
Sorting utf8mb4_0900_ai_ci orders Bengali acceptably for lists; the UI never promises dictionary collation
Search query and index are normalised identically (NFC, strip zero-width joiner/non-joiner where not semantic) before matching — see 06
Slugs Bengali titles produce either a transliterated ASCII slug (default, better for sharing) or the Bengali text percent-encoded; the choice is a site option, and existing slugs never change when it is flipped
Text direction LTR only. Bengali is LTR; no RTL support is claimed
Line breaking word-break: normal with overflow-wrap: anywhere on narrow containers; never break-all, which mangles conjuncts

Encoding hygiene: the database, connection, HTML and files are all UTF-8 (utf8mb4). Export files (CSV) carry a BOM so Excel opens Bengali correctly. Any tooling that round-trips files must preserve UTF-8 — on Windows, bulk edits run through Python or git, never through a shell that re-encodes.

12.4 Language switcher block#

Settings: display as flags, language codes, or full names; dropdown or inline; show the current language or not; hide when only one language is active (default on, so a single-language site never sees it even if the block is in the header).

It emits real links (/en/about), never JavaScript-only switching, so crawlers and hreflang agree with what a visitor can click.

12.5 Adding a language#

  1. Settings -> Site info -> Languages -> Add lists the locales KodePress ships strings for.
  2. Choose the locale, confirm. sites.locales is updated.
  3. KodePress offers to create translation stubs for all published pages (a draft copy per page), or to start empty.
  4. For a locale with no bundled admin strings, the admin UI falls back to English for missing keys and the screen says which keys are missing, with a CSV export/import to translate them.

Bundled admin locales at launch: bn, en. Others are a translation contribution, not a code change.

12.6 Tests for this document#

Test Asserts
lang_key_parity bn and en have identical key sets, no empty values
no_literal_ui_text grep sweep over views and components passes the allow-list
default_locale_unprefixed /about serves the default locale
second_locale_prefixed /en/about serves the translation
hreflang_alternates_emitted one per published sibling plus x-default
switcher_falls_back_to_home when the translation does not exist, no 404
dates_render_in_site_timezone_and_locale Bengali month name, Asia/Dhaka offset
bengali_slug_transliterates and an existing slug is untouched when the option changes
csv_export_bom Bengali survives an Excel-style read
cache_is_keyed_per_locale /about and /en/about are separate cache entries

KodePress documentation · generated from the Markdown sources by tools/build-docs-site.py · internal preview, not indexed.