I recently decided to localize my site using the sveltekit-i18n library, following this example. The problem is that in it the handle hook seems to mess with the load functions when redirecting using the default locale.
This is my version of the handle hook:
// hooks.server.js
import { defaultLocale, loadTranslations, locales } from '$lib/translations';
import { redirect } from '@sveltejs/kit';
const routeRegex = new RegExp(/^/[^.]*([?#].*)?$/);
/** @type {import('@sveltejs/kit').Handle} */
export const handle = async ({ event, resolve }) => {
const { url, request, isDataRequest } = event;
const { pathname, origin } = url;
// If this request is a route request
if (routeRegex.test(pathname)) {
// Get defined locales
const supportedLocales = locales.get().map((l) => l.toLowerCase());
// Try to get locale from `pathname`.
let locale = supportedLocales.find(
(l) => l === `${pathname.match(/[^/]+?(?=/|$)/)}`.toLowerCase()
);
// We want to redirect the default locale to "no-locale" path
if (locale === defaultLocale && !request.headers.get('prevent-redirect')) {
const localeRegex = new RegExp(`^/${locale}`);
const location = `${pathname}`.replace(localeRegex, '') || '/';
return new Response(undefined, { headers: { location }, status: 301 });
// If route locale is not supported
} else if (!locale) {
// Get user preferred locale if it's a direct navigation
// if (!isDataRequest) {
// locale =
// `${`${request.headers.get('accept-language')}`.match(/[a-zA-Z]+?(?=-|_|,|;)/)}`.toLowerCase();
// }
// Get user preferred locale if it's a direct navigation
if (!isDataRequest) {
locale = event.cookies.get('locale');
}
// Set default locale if user preferred locale does not match
if (!supportedLocales.includes(locale)) locale = defaultLocale;
if (locale === defaultLocale) {
const path = `${pathname}`.replace(//$/, '');
const redirectTo = `${origin}/${locale}${path}${isDataRequest ? '/__data.json?x-sveltekit-invalidated=100' : ''}`;
// We want to prevent redirect to fetch data for the default locale
request.headers.set('prevent-redirect', '1');
// Fetch the redirected route
const response = await fetch(redirectTo, request);
// Get response body and set html headers
const data = await response.text();
// Serve the redirected route.
// In this case we don't have to set the html 'lang' attribute
// as the default locale is already included in our app.html.
return new Response(data, {
...response,
headers: {
...response.headers,
'Content-Type': isDataRequest ? 'application/json' : 'text/html'
}
});
}
// 301 redirect
return new Response(undefined, {
headers: { location: `/${locale}${pathname}` },
status: 301
});
}
// Add html `lang` attribute
return resolve(
{ ...event, locals: { lang: locale } },
{
transformPageChunk: ({ html }) => html.replace('%lang%', `${locale}`)
}
);
}
return resolve(event);
};
// /** @type {import('@sveltejs/kit').HandleServerError} */
// export const handleError = async ({ event }) => {
// const { locals } = event;
// const { lang } = locals;
// await loadTranslations(lang, 'error');
// console.log(event);
// return locals;
// };
There are three locales en
, bg
, and pl
. The idea is however when navigating using the defaultLocale
(bg
) to remove it from the URL. For example somesite.eu/bg/services
becomes somesite.eu/services
. And it works as expected, but when navigating to a page that is dependent on data provided by a load function it just won’t trigger the load function. This behavior is only present when navigating using the defaultLocale
. Or in other words only when the handle hook tries to redirect to a non-localized URL. For example, navigation to somesite.eu/services
won’t trigger the +page.server.js
load function, but somesite.eu/en/services
will.
For this reason, this is the route structure that the example suggests:
.
└── src/
├── routes/
│ └── [...lang=locale]/
│ ├── route1/
│ │ ├── +page.svelte
│ │ └── +page.server.js
│ ├── route2/
│ │ ├── +page.svelte
│ │ └── +page.server.js
│ ├── route3/
│ │ ├── +page.svelte
│ │ └── +page.server.js
│ ├── +layout.js
│ ├── +layout.server.js
│ └── +error.svelte
├── app.html
└── +hooks.server.js
// params/locale.js
import { locales } from '$lib/translations';
/** @type {import('@sveltejs/kit').ParamMatcher} */
export function match(param) {
const definedLocales = locales.get();
const paths = [...definedLocales, ''];
const slashPaths = paths.map((l) => `${l}/`);
return [...paths, ...slashPaths].includes(param);
}
I tried to debug the problem but my understanding of sveltekit’s data loading is not on that level.