Open Lobidu opened 1 year ago
I can't load the translations inside the load function and pass the I18n object as part of data either, because only plain objects are allowed.
No. load
can return any kind of value. The only load
functions that are limited on the type of values it can return are the ones defined in a +layout.server.js
file, not the case here.
This worked very well for me:
src/routes/+layout.server.ts
:
export function load({ request }) {
return {
languages:
request.headers
.get("accept-language")
?.split(",")
?.map(lang => lang.split(";")[0].trim()) ?? [],
};
}
src/routes/+layout.ts
:
import { browser } from "$app/environment";
import { loadTranslations } from "$lib/utils/i18n";
export async function load({ url, data }) {
const languages = browser ? navigator.languages : data.languages;
return {
t: await loadTranslations(languages, url.pathname),
};
}
src/routes/+layout.svelte
:
<script lang="ts">
import { setContext } from "svelte";
export let data;
setContext("t", data.t);
</script>
<slot />
src/app.d.ts
:
declare module "svelte" {
export function getContext(key: "t"): Awaited<ReturnType<typeof import("$lib/utils/i18n")["loadTranslations"]>>;
}
src/lib/utils/i18n.ts
:
const config: Config = {
loaders: [
// ...
]
};
const availableLanguages = new Set(config.loaders?.map(loader => loader.locale) ?? []);
const defaultLocale = "en";
const defaultLocaleForLanguage: Record<string, string> = {
en: "en-US",
pt: "pt-BR",
};
export async function loadTranslations(languages: readonly string[], route: string) {
const locale =
languages.find(lang =>
availableLanguages.has(
!lang.includes("-") && lang in defaultLocaleForLanguage ? defaultLocaleForLanguage[lang] : lang,
),
) ?? defaultLocale;
const instance = new i18n(config);
await instance.loadTranslations(locale, route);
return instance.t;
}
Any page that uses translations:
<script lang="ts">
import { getContext } from "svelte";
const t = getContext("t");
</script>
<p>{$t("some-key")}</p>
Hey @lbguilherme - Thank you so much for your detailed answer. I stand corrected, this does indeed work.
Just one thing - It doesn't load any translations for the new route when switching routes on frontend only. I can use data.t
, but not getContext('t')
. Any idea why that is?
The solution above (https://github.com/sveltekit-i18n/lib/issues/106#issuecomment-1535196388) seems like a good way to avoid side-effects in load.
Shouldn't it be included in the examples?
Albeit, my use case is a pretty simple one, and I don't know whether this solution works 100% for more complex setups (e.g. what @Lobidu states here https://github.com/sveltekit-i18n/lib/issues/106#issuecomment-1642053056).
P.S. probably all of this would need reworked again after the introduction of runes.
Hey @lbguilherme - Thank you so much for your detailed answer. I stand corrected, this does indeed work.
Just one thing - It doesn't load any translations for the new route when switching routes on frontend only. I can use
data.t
, but notgetContext('t')
. Any idea why that is?
I currently have the same issue, did you manage to fix it?
EDIT: I made it work by passing the setContext in a reactive statement like this:
export let data;
$: if ($page) {
setContext("t", data.t);
}
Hi @CustomEntity, I've come to the same problem with translation loading, and also tried to employ the same solution of sharing the $t
store via the context API — this doesn't fully work, as you're passing a new object in each context. For example, it is not possible to change the language (client side) on one page, and react to that change on another page, because you would have two different stores from two different contexts.
Please text back if you're successful using your reactive setContext solution with dynamic client side language changing (e.g. $locale = 'en'
or setLocale('en')
), I'd be interested in trying it.
Hi @CustomEntity, I've come to the same problem with translation loading, and also tried to employ the same solution of sharing the
$t
store via the context API — this doesn't fully work, as you're passing a new object in each context. For example, it is not possible to change the language (client side) on one page, and react to that change on another page, because you would have two different stores from two different contexts.Please text back if you're successful using your reactive setContext solution with dynamic client side language changing (e.g.
$locale = 'en'
orsetLocale('en')
), I'd be interested in trying it.
Hello @mtpython, I finally encountered the same problem as yours, the solution worked on a single page but not on multi-page like you. So I've stopped using SvelteKit-i18n until they fix the problem on their side, the other libraries also have the same problem so I've decided to use a temporary DIY solution that works as well as SvelteKit-i18n for my use.
Thanks for mentioning the "poly-i18n", but for my project the currently missing lazy load feature is more important than the downsides of race conditions, so until a more complete/mature solution is released, I'd resort to just accepting the current state of this library as is...
tl;dr: this issue should remain open
lbguilherme commented on May 4 This worked very well for me:
Your solutions seem to work for me as well, but there is type error:
Cannot use 't' as a store. 't' needs to be an object with a subscribe method on it.
<script lang="ts">
import {getContext} from 'svelte'
const t = getContext('t')
</script>
<div>
<h2>{$t('home.title')}</h2>
<p>{$t('home.content')}</p>
</div>
Wow, glad I found this issue. I was going crazy about why translations kept doing crazy things on production but always worked locally and on staging. The solution by lbguilherme worked for me. But I had to disable lazy loading for different routes or they wouldn't load in SPA mode. I guess this should be mentioned in the docs.
Unfortunately I see a fundamental design flaw in the way sveltekit-i18n implements the localisation store: If I see it correctly it uses a global store to manage localisation state. On most SSR environments this means localisation state is unintentionally shared between users.
For most smaller applications this doesn't have any visible effect because localisation state is set at the beginning of the request and the request is completed before another user's request is received.
But in high traffic environments and / or slow rendering pages the following could happen:
en
. State is set, the page starts rendering with localeen
.cn
. This modifies the globallocale
state.cn
!The Sveltekit docs explicitly state to avoid global stores for exactly this reason:
https://kit.svelte.dev/docs/state-management
The better way to solve this would be to utilise
setContext
on the root layout to get a contextualised store. I've tried to do this in my app like this:This is working fine client-side. But server side no translation happens at all, due to the fact that the line
$: i18n.loadTranslations(data.locale, data.path);
returns a promise that isn't resolved before Sveltekit returns the rendered page.await
ing that promise before mounting<slot/>
just makes SSR return no DOM at all for that slot.The idiomatic way to
await
something would be inside theload
function instead. But inside theload
function I can't accesscontext
. I can't load the translations inside the load function and pass theI18n
object as part ofdata
either, because only plain objects are allowed.Any Idea on how to get out of this pickle? The easiest way would be to make
loadTranslations
blocking.