Internationalisation, Localisation, And Content
This page explains how locale, localised routes, translated UI, and customer content actually fit together in Coil.
What Is This?
Coil treats internationalisation as a combination of:
- request-time locale resolution
- site-aware locale policy
- localised routes
- translated UI strings
- localised content and formatting
Those are related, but they are not the same problem.
Why Does It Exist?
Real customer apps need locale to influence more than copy:
- routing
- canonical and alternate URLs
- formatting
- search-facing metadata
- cache keys
- customer-visible controls
If locale is handled only in frontend code, the application model becomes incoherent.
When Should I Use This Model?
Use this model whenever you:
- add a second locale
- add a new site with a different default locale
- decide whether copy should live in a translation dictionary or in localised content
- choose between frontend translation keys and server-rendered localised strings
How Locale Resolution Works
Coil resolves:
- site from host
- locale inside that site’s allowed locale set
- route under that site-and-locale context
That means route matching, render values, and SEO all agree about what the user is actually seeing.
The runtime code for that lives in:
crates/coil-runtime/src/http/routing/model.rs
Translation Dictionaries Versus Localised Content
Keep these separate.
Translation dictionaries
Use for:
- nav labels
- button text
- control labels
- small explanatory UI strings
Today’s checked-in example is Gitly’s theme-side dictionary in:
apps/gitly/theme/assets/site.js
Localised content
Use for:
- CMS content
- product descriptions
- account messaging produced by runtime code
- SEO-relevant content bodies
This should come from managed content or render-model values, not from a frontend dictionary.
Server-Rendered I18n Versus Demo Translation Dictionaries
Current honest state:
- Coil core already ships server-side locale primitives, locale contexts, fallback chains, locale-aware URL routing, translation catalogs, and a translation runtime
- Coil does not yet ship a first-class customer-facing translation file convention plus a template-native translation helper
- Gitly demonstrates a customer-owned theme-side dictionary in
apps/gitly/theme/assets/site.js - Shoppr demonstrates server-rendered locale-aware values, site-aware rendering, and now server-shaped route-aware market and locale switch targets
So if you ask “can Coil support server-rendered i18n?”, the answer is yes.
If you ask “does the current public demo show a full customer translation catalog loaded into templates on the server?”, the honest answer is no, not yet.
That distinction matters:
- the platform primitives exist
- the current checked-in demo translation story is still incomplete
- Gitly's
site.jsis a demo convention, not the framework limit
Key Naming Patterns
If you adopt a translation-key dictionary today, use stable semantic keys.
Gitly’s checked-in pattern is a good model:
- page or area prefix:
home,explore,search,actions - grouped control keys:
controls.language,controls.dark - grouped navigation keys:
nav.home,nav.actions
Examples from apps/gitly/theme/assets/site.js:
home.titlehome.summarysearch.emptycontrols.themenav.profile
Template Translation Examples
Server-rendered value
<h1 coil:text="${page.title}">Fallback</h1>
<p coil:text="${account.state_summary}">Fallback summary</p>
This is the right pattern for first-render, transactional, and SEO-relevant copy.
Customer-owned translation-key convention
<h1 data-i18n="home.title">One Coil app can look like a forge.</h1>
<button type="button" data-i18n-control="dark">Dark</button>
This is the checked-in Gitly pattern for theme and demo UI strings. It is not the only possible Coil i18n model, and it should not be mistaken for the full platform contract.
Fallback Examples
Fallback locale is useful for:
- temporary UI-string fallback
- stable locale defaults
- route generation when a localised path needs a deterministic default
Fallback locale is not a substitute for:
- publishing localised customer content
- product translation discipline
Current runtime configs show this explicitly:
apps/shoppr/platform.tomlapps/shoppr/platform.dev.tomlapps/gitly/platform.tomlapps/gitly/platform.dev.toml
English, French, And Polish Shoppr Example
Shoppr is the canonical multi-site, multi-locale example.
Relevant files:
apps/shoppr/app.tomlapps/shoppr/platform.tomlapps/shoppr/platform.dev.tomlapps/shoppr/catalog.toml
What it demonstrates:
shoppr-ukwith default localeen-GBshoppr-frwith default localefr-FRshoppr-plwith default localepl-PL- one customer app
- site-aware branding
- localised routes
- site-specific availability in
catalog.toml
This is the example to follow when adding a new locale and deciding whether it should also be a new site.
How To Add A New Locale
The practical sequence is:
- add the locale to app-level
supported_locales - add it to the appropriate site’s
supported_locales - decide whether the site’s
default_localeshould change - update customer-owned translation dictionaries if you use them
- update server-rendered localised content or data if the page content is localised
- verify localised routes and canonical behaviour in the running app
If the host, brand, or assortment also changes, you likely need a new site, not just a new locale.
Constraints And Common Mistakes
Pretending the framework already owns translation-file format and lookup
It does not. Document the customer convention honestly.
Treating locale as only text replacement
Locale also affects routing and metadata.
Putting customer content into config files
Config describes locale policy. It should not become your CMS.
Confusing site with locale
Sites choose public brand and host boundary. Locales choose language and formatting within that boundary.
What Should I Read Next?
- Internationalisation
- SEO
- Sites, Locales, And Markets
apps/shoppr/app.tomlapps/gitly/theme/assets/site.js