Localized Routes
Introduction
By default, every page in ReeWeb has the same URL path in every language - /about/ is /about/ in English, Slovenian, and German. Localized routes let you translate the path itself: /about/ in English becomes /o-nas/ in Slovenian and /ueber-uns/ in German. The translation files declare a route_name key, and the script generates the appropriate per-language URLs automatically.
Setting a Route Name
Add a route_name key to any section's translation object. The key name is the canonical URL segment, and the value is the translated path segment:
// src/public/en.json
{
"about": {
"route_name": "about",
"title": "About us"
},
"contact": {
"route_name": "contact",
"title": "Contact"
}
}
// src/public/sl.json
{
"about": {
"route_name": "o nas",
"title": "O nas"
},
"contact": {
"route_name": "kontakt",
"title": "Kontakt"
}
}
With these in place, the SSG script generates:
/about/(English)/o-nas/(Slovenian)/contact/(English)/kontakt/(Slovenian)
The route_name value is processed through slugify() in lib/route_aliases.ts, which transliterates Unicode characters to ASCII and normalises the result to a URL-safe slug:
slugify("o nas"); // → "o-nas"
slugify("übersetzen"); // → "ubersetzen" (diacritics removed)
slugify("straße"); // → "strasse" (ß → ss)
Route Name Isolation
A critical rule: route_name is never inherited across languages. If English defines about.route_name but Slovenian doesn't, the Slovenian URL stays as /about/ - it does not inherit the English "about". This is enforced in lib/i18n.ts:
// Never inherit route_name from other languages.
if (key === "route_name") continue;
This means you must define a route_name in every language where you want a localized path. This is intentional - a Slovenian URL like /about/ using an English word is fine, but a Slovenian URL like /ueber-uns/ with a German word would be confusing. Each language controls its own paths.
Nested Localized Routes
Route names work at any nesting depth. For a blog post at blog/my-post-slug:
// sl.json
{
"blog": {
"route_name": "novice"
}
}
This produces /novice/my-post-slug/ in Slovenian while staying at /blog/my-post-slug/ in English. The route map in lib/static_site.ts (build_static_route_map()) walks the translation tree segment by segment, substituting route_name where present and keeping the canonical segment where not.
How It Affects Links
When you link between pages, always use the canonical path - not the localized URL. The localized_path() helper resolves the correct URL for the active language at render time:
<!-- Correct: use canonical path, helper resolves the right URL -->
<a href="{~ localized_path('/about') }">About</a>
<!-- Wrong: hard-coding a localized path breaks in other languages -->
<a href="/o-nas/">About</a>
For links to the same page in a different language, use the localized_path_for_lang() helper:
{#each props.active_languages as target_lang}
<a href="{~ localized_path_for_lang(target_lang, '/about') }"> {= props.language_names[target_lang] } </a>
{/each}
This resolves /about/ to the correct URL in any language - /about/ for English, /o-nas/ for Slovenian.
Hreflang Alternate Links
When --site-url is provided to the SSG script, every page gets <link rel="alternate" hreflang="..."> tags pointing to each language variant - including an x-default entry pointing to the default language. These are required by Google for multi-language SEO.
<link rel="alternate" hreflang="en" href="https://example.com/about/" />
<link rel="alternate" hreflang="sl" href="https://example.com/o-nas/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/o-nas/" />
The x-default points to the default language (Slovenian in the example above), which is what unauthenticated users see when they land on the root URL.