Default language without a prefix
With
strategy: 'prefix_except_default', the default option, the URLs of the default language do not change and every other language lives under its prefix. The module generates the
/en/…
routes from the existing pages, with the same file, layouts and options: there is no need to duplicate pages.
i18n: {
defaultLocale: 'es',
catalogs: { es: { common: 'app/locales/es/common.json' }, en: { common: 'app/locales/en/common.json' } },
// By default: es without a prefix and en under /en/…
strategy: 'prefix_except_default',
// No /en/… versions and no hreflang:
exclude: ['/admin/*'],
// A language with a partial translation only has these routes:
partial: { ar: ['/playground'] },
}
// app/pages/guide.meeko -> /guide and /en/guide
// app/pages/docs/[...slug].meeko -> /docs/… and /en/docs/…
// app/pages/en/legal.meeko -> replaces the generated /en/legal
A project page at the URL of another language, such as
app/pages/en/legal.meeko, replaces the generated route: that is how the prose of a page is written in each language.
exclude
leaves a route without versions and
partial
limits the ones of a language with a partial translation. A URL with the prefix of the default language, such as
/es/guide, does not exist.
Localized links
<script lang="ts">
const i18n = useI18n()
</script>
<template>
<!-- /guide in Spanish, /en/guide in English -->
<a bind:href="i18n.localePath('/guide')">{i18n.t('nav.guide')}</a>
<UILink bind:href="i18n.localePath('/docs/intro#setup')">Intro</UILink>
</template>
localePath
adds the prefix of the document's language, or of the language it receives, and keeps the search and the fragment. Use it in the internal links of the interface.
Language switcher
<script lang="ts">
const i18n = useI18n()
</script>
<template>
<nav aria-label="Language">
<a
each:of="i18n.pageLocales()"
each:as="code"
each:key="code"
bind:href="i18n.switchLocalePath(code)"
bind:hreflang="code"
bind:lang="code"
bind:aria-current="code === i18n.locale ? 'page' : undefined"
>
{i18n.localeName(code)}
</a>
</nav>
</template>
<!-- /playground?tab=demo#messages -> /en/playground?tab=demo#messages -->
switchLocalePath
returns the equivalent route of the current page in another language and
pageLocales
the languages that have a version of it. Use real links: they work without JavaScript and search engines follow them. Changing the language is a full navigation that stores the choice in a cookie;
switchLocale
does the same from a script.
hreflang and sitemap
<!-- /en/guide, with site: 'https://example.com' -->
<html lang="en" dir="ltr">
<link rel="alternate" href="https://example.com/guide" hreflang="es">
<link rel="alternate" href="https://example.com/en/guide" hreflang="en">
<link rel="alternate" href="https://example.com/guide" hreflang="x-default">
<!-- sitemap.xml (sitemap: true) -->
<url>
<loc>https://example.com/en/guide</loc>
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/guide"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/guide"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/guide"/>
</url>
Each localized page declares
lang,
dir
and one
link rel="alternate"
per language plus
x-default, with absolute URLs based on
site. With
sitemap: true, Meeko's
sitemap.xml
lists each version with its alternates.
Suggest the language, without redirecting
<script lang="ts">
const i18n = useI18n()
const suggestion = signal<string | undefined>(undefined)
// Only in the browser, after hydrating: it never redirects.
onMounted(() => { suggestion.value = i18n.suggestedLocale() })
</script>
<template>
<p if:when="suggestion.value" role="status">
<a bind:href="i18n.switchLocalePath(suggestion.value!)">
{i18n.localeName(suggestion.value!)}
</a>
<button type="button" on:click="() => { i18n.rememberLocale(i18n.locale); suggestion.value = undefined }">
{i18n.localeName(i18n.locale)}
</button>
</p>
</template>
URLs without a prefix always show the default language: the module never redirects based on
Accept-Language
or the cookie. That way each URL has a single content that search engines can index and caches can share.
suggestedLocale
proposes, only in the browser, the language of the cookie or of
navigator.languages
so that you can show a notice.
URL utilities
import { localeOf, localizedPath, negotiateLocale, stripLocale } from '@meekojs/i18n'
const locales = ['es', 'en', 'ar']
localeOf('/en/playground', locales, 'es') // 'en'
localeOf('/playground', locales, 'es') // 'es'
localizedPath('/playground?q=1#form', 'en', locales, 'es')
// '/en/playground?q=1#form'
localizedPath('/en/playground', 'es', locales, 'es') // '/playground'
stripLocale('/en/playground', locales) // '/playground'
negotiateLocale('en-US,en;q=0.9,es;q=0.8', locales) // 'en'
localeOf
returns the language of the first segment or the fallback it receives.
localizedPath
replaces a known prefix and omits the one of the default language;
negotiateLocale
chooses a configured language from an
Accept-Language
header or from a list of preferences.