Localized Routes

By default, every page in ReeWeb has the same URL path in every locale - /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-locale 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:

route_name is a top-level key in the route directory's own {locale}.json, not a nested entry in the site-wide root file. /about/ takes its route name from src/public/about/{locale}.json:

// src/public/about/en-us.json
{
    "route_name": "about",
    "ui": { "title": "About us" }
}
// src/public/about/sl-si.json
{
    "route_name": "o nas",
    "ui": { "title": "O nas" }
}
// src/public/contact/sl-si.json
{
    "route_name": "kontakt"
}

Putting { "about": { "route_name": "o nas" } } in the root src/public/sl-si.json does not work and reports no error - the root file loads into the routes namespace, while the route map looks for the segment's own namespace. The build just keeps the untranslated /sl-si/about/ path. This is why each localized route needs a {locale}.json inside its own directory.

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"  (combining diacritic stripped)
slugify("straße"); // → "strasse"        (ß → ss)
slugify("Łódź"); // → "lodz"             (ł → l, NFKD cannot decompose it)
slugify("Ærø"); // → "aero"              (æ → ae, ø → o)

Letters NFKD decomposes lose their accent. Letters it cannot decompose have an explicit mapping - ß, æ, œ, ø, ł, đ, ð, þ, ħ, ı, ŋ, ŧ, ƶ - because without one they are removed outright rather than simplified. Anything still outside [a-z0-9_] becomes a hyphen.

Route Name Isolation

A critical rule: route_name is never inherited across locales. 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 locales.
if (key === "route_name") continue;

This means you must define a route_name in every locale 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 locale controls its own paths.

Nested Localized Routes

Route names work at any nesting depth. For a blog post at blog/my-post-slug:

// sl-si.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.

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 locale 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 locales -->
<a href="/o-nas/">About</a>

For links to the same page in a different locale, use the localized_path_for_locale() helper:

{#each props.active_locales as l}
<a href="{~ localized_path_for_locale(l, props.canonical_path) }"> {= props.locale_names[l] } </a>
{/each}

This resolves /about/ to the correct URL in any locale - /about/ for English, /o-nas/ for Slovenian.

When --site-url is provided to the SSG script, every page gets <link rel="alternate" hreflang="..."> tags pointing to each locale variant - including an x-default entry pointing to the default locale. These are required by Google for multi-locale SEO.

<link rel="alternate" hreflang="en-US" href="https://example.com/about/" />
<link rel="alternate" hreflang="sl-SI" 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 locale (Slovenian in the example above), which is what unauthenticated users see when they land on the root URL.