i18n
Localized URLs
Give each language of a VitNode app its own URLs, with a /pl prefix by default and translated paths such as /pl/odkrywaj through i18n.routePaths.
Every language you add gets its own URLs. With English as the default, Polish pages live under /pl/... without any extra setup. You can go one step further and translate the paths themselves, so /discover becomes /pl/odkrywaj for Polish readers. It's the same route, the same component and the same <Link to="/discover">, with a second spelling.
Before you begin
- A second language in
i18n.locales. Languages adds one withvitnode i18n create.
Check the prefix
Restart dev after adding the language. Every public page now answers in Polish under a /pl prefix: /discover is English, /pl/discover is Polish. The language switcher in the header (LanguageSwitcher from @vitnode/core/tanstack/layout, rendered once the app has more than one language) moves between them.
Find the English path
Every plugin documents the paths its pages answer to. Core's are /discover, /search, /users/:nameCode, /login, /settings and friends. Those English paths are the keys you translate. Your app's own routes work the same way.
Translate it in vitnode.config.ts
Add routePaths, keyed by locale:
export const vitNodeConfig = buildConfig({
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', name: 'English' },
{ code: 'pl', name: 'Polski' },
],
routePaths: {
pl: {
'/blog/article/:slug': '/blog/artykul/:slug',
'/discover': '/odkrywaj',
'/search': '/szukaj',
'/users/:nameCode': '/uzytkownicy/:nameCode',
},
},
timeZone: 'UTC',
},
})This is the configuration this site runs on.
Restart dev
Every link, redirect, canonical URL and sitemap line now uses the new spelling. You don't touch any route file.
| Page | English | Polish before | Polish after |
|---|---|---|---|
| A post | /blog/article/hello | /pl/blog/article/hello | /pl/blog/artykul/hello |
| Discover | /discover | /pl/discover | /pl/odkrywaj |
| Search | /search | /pl/search | /pl/szukaj |
| A profile | /users/jane-1 | /pl/users/jane-1 | /pl/uzytkownicy/jane-1 |
| Login | /login | /pl/login | /pl/login (untranslated) |
The old spellings keep working. /pl/discover now redirects to /pl/odkrywaj, so links people already shared don't break.
One route, many spellings
A translation never creates a route. The router's URL rewrite does the work in both directions:
- Coming in,
/pl/odkrywajis read as the Polish version of/discover, so the route tree matches/discover. - Going out,
<Link to="/discover">renders/pl/odkrywajfor a Polish reader.
Route files, route IDs, lazy-loaded chunks and every to="..." in your code stay exactly as they were. See Navigation for how the rewrite fits around Link.
Translation rules
- Keys are English source paths in VitNode syntax:
:paramfor a parameter and a trailing*for a catch-all. They must be real routes in your app, or the build tells you which one you probably meant. - Only static segments get translated. A parameter keeps its value:
jane-1staysjane-1in/pl/uzytkownicy/jane-1. - Parameters must match exactly. You can't drop one, add one or rename one (
:nameCodestays:nameCode). A*catch-all has to stay the last segment. - Lowercase only. Non-ASCII letters are fine and get percent-encoded:
'/files': '/zaÅÄ czniki'goes out as/pl/za%C5%82%C4%85czniki, and the browser showszaÅÄ czniki. - Don't start with a locale code.
/pl/...or/en/...as the first segment would be read as a language prefix. /can't be translated. The home page is spelled bylocalePrefix./adminand/apiare never localized. They carry no prefix and no translation.- A missing translation falls back to English.
/loginwithout an entry is/pl/login, so a half-translated app still works.
A translated layout and a translated parameter route have two extra rules; Advanced localized URLs covers both.
localePrefix modes
| Mode | English | Polish | Notes |
|---|---|---|---|
as-needed (default) | /discover | /pl/odkrywaj | The default locale has no prefix. /en/... redirects away. |
always | /en/discover | /pl/odkrywaj | Every locale is prefixed. / redirects to /en. |
never | /discover | /odkrywaj | No prefix. Needs one locale per host, see below. |
never needs a domain per language; see A domain per language.
Switching languages
The language switcher keeps you on the same page, with the same parameters, query and hash. /pl/uzytkownicy/jane-1 switches to /users/jane-1.
Pages whose record has a different slug per language (see Translated segments vs localized slugs) declare their alternates, and the switcher follows them to the other language's own slug. If the language you pick isn't among the declared alternates, because that translation isn't published, you land on that language's home page instead of a 404. Switching to a language on another domain is a full page load.