Internationalization in SvelteKit

Hi, I am wondering how to set up locales with SvelteKit. The structure of the documentation is somewhat confusing. I am supposed to set up my routes inside [locale] , however, the +Layout.server.js and the +Layout.svelte are located outside the [[preview=preview]] directory. I am honestly lost on how I am supposed to set up locales. I tried placing [[preview=preview]] inside [locale] and received a 404 error. I also tried setting up [locale] inside [[preview=preview]] and encountered errors too. Furthermore, the +Layout.server.js and +Layout.svelte do not recognize the locale change since they are outside. Can someone help me and explain the steps so that I can understand what I need to do? Visuals would be helpful, if possible.

I honnestly don't understand the doc as it doesnt really show what I am supposed to code in order to be able to switch between my locales. There is a tutorial in next.js, but It doesnt apply like for sveltkit.
Capture d’écran 2024-05-08 171650

Hey @janisgaudreault.jg,

Thanks for raising this question! We don't have a recommended implementation for SvelteKit i18n yet, but what you're encountering seems to be the expected behavior with SvelteKit.

Routes wrapped in [[double brackets]] will be ignored by the SvelteKit router wherever possible. So, when SvelteKit receives a request for /fr-fr/bonjour, it will attempt to resolve it first with src/routes/[[preview=preview]]/[uid]/+page.svelte, without considering src/routes/[[preview=preview/[[lang]]/+page.svelte. The [[lang]] parameter actually has 0 priority in the SvelteKit router, so it will be ignored if at all possible. You can fix this by making the lang parameter mandatory: [lang=lang] . However, that means duplicating your code:

.
└── src/
    └── routes/
        └── [[preview=preview]]/
            β”œβ”€β”€ [lang=lang]/
            β”‚   β”œβ”€β”€ [uid]/
            β”‚   β”‚   └── +page.svelte <!-- /fr-fr/bonjour -->
            β”‚   └── +page.svelte <!-- /fr-fr -->
            β”œβ”€β”€ [uid]/
            β”‚   └── +page.svelte <!-- /hello -->
            └── +page.svelte <!-- / -->

(More about the SvelteKit router)

The param matchers (src/params/lang.js) are important here, to make sure that lang only matches fr-fr and not bonjour.

See an example implementation here:

Sam

Thank you for your reply; it has brought me closer to a solution. However, I'm unsure how to handle the +layout.server and + layout.svelte" files outside of the [[preview=preview]] file, which contain the navigation and footer data. A senior programmer assisted me and proposed the following solution:

Capture d’écran 2024-05-09 142330

In this setup, while the page content changes languages based on the URL, the navigation and footer fetched in the +layout do not. What should I do to resolve this? I guess I can set +layout files in each repo for each locales... ? :thinking:

Hey Janis,

You could certainly use a catch-all route. However, that will force you to handle the routing logic manually, which I find a little stressful personally :sweat_smile:

If you don't mind duplicating your +page.svelte files in a few places, then I think you can avoid any catch-all routes.

There's a convenient solution for the layout. SvelteKit provides a global store for all the data you query in your application:

<script>
  import { page } from { $app/stores }
</script>

<pre>
  <!-- This will display all of the data fetched in your application -->
  {JSON.stringify($page)}
</pre>

You can use this in your +layout.svelte file to use the data fetched in your +page.server.js file (we already do this to handle metadata in the starter projects).

Therefore, you can query your global data in +page.server.js and then access it from $page in your +layout.svelte to get a localized header and footer. For example:

// src/routes/[[preview=preview]]/[lang=lang]/[uid]/+page.server.js

import { asText } from '@prismicio/client';

import { createClient } from '$lib/prismicio';

export async function load({ params, fetch, cookies }) {
	const client = createClient({ fetch, cookies });

	const settings = await client.getSingle('settings', { // πŸ‘ˆ Fetch your global data localized
		lang: params.lang || 'en-us'
	});

	const page = await client.getByUID('page', params.uid, {
		lang: params.lang
	});

	return {
		settings, // πŸ‘ˆ Return your global data
		page,
		title: asText(page.data.title),
		meta_description: page.data.meta_description,
		meta_title: page.data.meta_title,
		meta_image: page.data.meta_image.url
	};
}

export async function entries() {
	const client = createClient();

	const pages = await client.getAllByType('page');

	return pages.map((page) => {
		return { uid: page.uid };
	});
}

and:

<!-- src/routes/+layout.svelte --> 

<script>
	import { PrismicPreview } from '@prismicio/svelte/kit';
	import { page } from '$app/stores';
	import { repositoryName } from '$lib/prismicio';
</script>

<svelte:head>
	<title>{$page.data.title}</title>
	{#if $page.data.meta_description}
		<meta name="description" content={$page.data.meta_description} />
	{/if}
	{#if $page.data.meta_title}
		<meta name="og:title" content={$page.data.meta_title} />
	{/if}
	{#if $page.data.meta_image}
		<meta name="og:image" content={$page.data.meta_image} />
		<meta name="twitter:card" content="summary_large_image" />
	{/if}
</svelte:head>

<header>
	<nav>
		<!-- πŸ‘‡ Use your global data -->
		<h1>{$page.data.settings.data.website_title}</h1> 
	</nav>
</header>

<ul>
	<li><a href="/">English</a></li>
	<li><a href="/fr-fr">French</a></li>
</ul>

<main>
	<slot />
</main>

<PrismicPreview {repositoryName} />

I've updated the Stackblitz example:

Let me know if that helps!

Sam

Big thanks to your patience and replies, I am working on it and so far so good. Have a great day! :slight_smile:

1 Like

Thanks to you for asking the question! This is exactly the kind of thing we need to figure out for our documentation :slight_smile:

Hey, the page switches languages. However, check out this error:
[vite:dev] TypeError: Cannot read properties of undefined (reading 'data')
[vite:dev] at C:\Users\janis\screen-website\src\lib\components\ProgressBar.svelte:5:38
[vite:dev] at Object.$$render (C:\Users\janis\screen-website\node_modules\svelte\src\runtime\internal\ssr.js:156:16)
[vite:dev] at eval (C:/Users/janis/screen-website/src/routes/+layout.svelte:77:140)
[vite:dev] at Object.$$render (C:\Users\janis\screen-website\node_modules\svelte\src\runtime\internal\ssr.js:156:16)
[vite:dev] at C:\Users\janis\screen-website.svelte-kit\generated\root.svelte:44:40
[vite:dev] at $$render (C:\Users\janis\screen-website\node_modules\svelte\src\runtime\internal\ssr.js:156:16)
[vite:dev] at Object.render (C:\Users\janis\screen-website\node_modules\svelte\src\runtime\internal\ssr.js:164:17)
[vite:dev] at Module.render_response (C:\Users\janis\screen-website\node_modules@sveltejs\kit\src\runtime\server\page\render.js:171:29)
[vite:dev] at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[vite:dev] at async Module.render_page (C:\Users\janis\screen-website\node_modules@sveltejs\kit\src\runtime\server\page\index.js:234:15)
[vite:dev] {
[vite:dev] error: { message: 'Internal Error' },
[vite:dev] params: { uid: 'favicon.ico' },
[vite:dev] route: { id: '/[[preview=preview]]/[uid]' },
[vite:dev] status: 500,
[vite:dev] url: URL {
[vite:dev] href: 'http://localhost:5173/favicon.ico',
[vite:dev] origin: 'http://localhost:5173',
[vite:dev] protocol: 'http:',
[vite:dev] username: '',
[vite:dev] password: '',
[vite:dev] host: 'localhost:5173',
[vite:dev] hostname: 'localhost',
[vite:dev] port: '5173',
[vite:dev] pathname: '/favicon.ico',
[vite:dev] search: '',
[vite:dev] searchParams: URLSearchParams {},
[vite:dev] hash: ''
[vite:dev] },
[vite:dev] data: {},
[vite:dev] form: null,
[vite:dev] state: {}
[vite:dev] }

src\routes[[preview=preview]][lang=lang]+page.server.js

import { asText } from '@prismicio/client';

import { createClient } from '$lib/prismicio';

// @ts-ignore
export async function load({ fetch, cookies, params }) {
	const client = createClient({ fetch, cookies });
	let settings, nav, errors, social;

	const page = await client.getByUID('page', 'accueil', {
		lang: params.lang
	});

	nav = await client.getSingle('navigation', {
		lang: params.lang || 'fr-ca'
	});

	settings = await client.getSingle('settings', {
		lang: params.lang || 'fr-ca'
	});

	errors = await client.getSingle('errors', {
		lang: params.lang || 'fr-ca'
	});


	social = await client.getSingle('socials', {
		lang: params.lang || 'fr-ca'
	});

	return {
		page,
		nav,
		settings,
		errors,
		social,
		title: asText(page.data.title),
		meta_description: page.data.meta_description,
		meta_title: page.data.meta_title,
		meta_image: page.data.meta_image.url
	};
}

export function entries() {
	return [{}];
}


Is it me or is the lang parameter catching favicon so when I am on my http://localhost:5173/ everything works, but when on the http://localhost:5173/en-us/home I get these errors since it doesn't get the correct parameter. This happend since I added the data that I was fetching from my +layout.server file before.