-
Notifications
You must be signed in to change notification settings - Fork 76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow non-singelton locale initialization, for supporting multi-language&SSR&async server #165
Comments
Hey @Tal500 👋 This one's a long coming one. Currently, this is indeed not supported and will require some refactoring. Mainly, we will need to change how we get the stores, moving them inside some factory method. What I imagine is something like: Initializing: // src/i18n.ts
+import { createI18nClient } from 'svelte-i18n'
+const i18n = createI18nClient(options)
+const { t, locale, formatDate, ... } = i18n
+export { t, locale, formatDate, ... } Using: -import { t } from 'svelte-i18n'
+import { t } from 'src/i18n.ts' We could still support importing directly from the module for the majority of use cases, if folks think it's important, by having something like // src/i18n.ts
import { createi18nClient, setClient } from 'svelte-i18n'
const i18n = createi18nClient(options)
const { t, locale, formatDate, ... } = i18n
+setClient(i18n)
export { t, locale, formatDate, ... } Note: this is not implemented |
Thanks @kaisermann ! I should notice that after a little bit of checking I'm not sure Svelte is even capable to handle async requests. In sapper, the middleware return a non-async function, so ExpressJS/Polka will only process one request at a time. (tested in Polka by me) But as I said, I'm not sure that Svelte can handle async at SSR. |
Well, turned up I was completely wrong (and my previous testing was wrong as well) Sapper is a middleware for Express/Polka. The fact that Express/Polka could (and in reality for a production website, will!) process many request at a time should worry you about the singleton global initialization of svelte-i18n! I believe it will be fixed after Svelte will be loaded at the client code, but still it is very disturbing to know it might (and will) happen! Do you agree that this issue is very serious @kaisermann? I believe that anyone that use it for production shell execute different instance of Sapper/SvelteKit servers by each locale, and redirect request to the correct server by reading the locale cookie in HTTP request. |
Hi @kaisermann, here is my suggestion according to your preferred syntax. First, it should keep support for the currently support syntax, and add a new recommended syntax for future code. As you said, we need to have some "i18n Client" object, probably represented directed by the funcs Additionally, I think we need to take advantage of Svelte context management functions functions (available only during component initialization). Proposed User UsageThe user can write the initialization code in // src/i18n.js
import { register, createI18nClient } from 'svelte-i18n';
register('en', () => import('./en.json'));
register('en-US', () => import('./en-US.json'));
register('pt', () => import('./pt.json'));
function setupI18nClient(locale?) {
const client = createI18nClient({
fallbackLocale: 'en',
initialLocale: locale,
});
return client;
} And in the main Svelte component ( // Main Svelte component (e.g. App.svelte or src/routes/_layout.svelte)
...
<script>
...
import { getLocaleFromNavigator, setI18nClientInContext } from 'svelte-i18n';
import { setupI18nClient } from '../i18n';
const i18nClient = setupI18nClient(getLocaleFromNavigator());
setI18nClientInContext(i18nClient);// Set i18n Client in Svelte context
...
</script>
... If the user use SSR, she may pass the locale data in session data, and wait for locale dictionary loading before returning. // src/routes/_layout.svelte
<script context="module">
import { waitLocale } from 'svelte-i18n';
export async function preload(page, session) {
await waitLocale(session.locale);
}
</script>
<script>
...
import { get } from 'svelte/store';
import { stores } from '@sapper/app';
import { getLocaleFromNavigator, setI18nClientInContext } from 'svelte-i18n';
import { setupI18nClient } from '../i18n';
const { session } = stores();
const locale = (typeof window !== 'undefined') ? getLocaleFromNavigator() : get(session).locale;
const i18nClient = setupI18nClient(locale);
setI18nClientInContext(i18nClient);// Set i18n Client in Svelte context
if (typeof window !== 'undefined') {
// TODO: Set here a cookie in client side with the value of locale=getLocaleFromNavigator()
}
...
</script>
... Now all components can use formatting this way (including 3rd parties): // Some Svelte component
...
<script>
...
import { getI18nClientFromContextOrGlobal } from 'svelte-i18n';
{ _, locale, formatDate } = getI18nClientFromContextOrGlobal();
...
</script>
... The function Implementation detailsI looked over the directory structure of svelte-18n code. The tough thing require a major refactoring is implementing the function I will speak about how to implement the rest of the functions, that's easy: // Internal code of svelte-i18n
import { setContext, getContext } from 'svelte';
const key = {};
// All the functions below can be called only in Svelte component initialization
export function setI18nClientInContext(i18nClient) {
setContext(key, i18nClient);
}
export function clearI18nClientInContext() {
setContext(key, undefined);
}
export function getI18nClientFromContextOrGlobal() {
var i18nClient = getContext(key);
if (i18nClient) {
return i18nClient;
} else {
// Return the global singleton client that was initialized by 'init()', or throw an exception if none.
}
} What do you think? |
To address SSR (Server-Side Rendering) generation, you can follow this approach in your Svelte application: Call #In [[lang]]/+page.ts
import { waitLocale } from 'svelte-i18n';
export async function load({ params }): Promise<Record<string, string>> {
const lang = params.lang || "en";
await waitLocale(lang);
return {
lang
};
} Pass the # Any svelte component
<script lang="ts">
import { _, json } from "svelte-i18n";
import { page } from "$app/stores";
</script>
{$_("box.readmore", {locale: $page.data.lang})}
OR
{$json("box.readmore", $page.data.lang)} Control that you never set the This is enough for SSR. But since we are not using the local store anymore, you can add the code below in your main +layout.svelte component to restore the reactive update of the 'lang' attribute of the HTML document during client-side rendering (CSR): #+layout.svelte
...
$: if (browser) document.documentElement.setAttribute("lang", $page.data.lang);
... I think it would be advisable to revise the SSR example in the documentation. |
The Problem
The initialization of svelte-i18n sets the preferred language in a global variable.
While this is acceptable for client, it is not acceptable for the server (with SSR).
The server might be async, in which it can proccess at the same time both request for one language and a request for other language.
However, as far as I can see, setting a new locale will override the existing one, and a race condition might occur.
Solution?
Allow to configure the current locale per request and not only globally.
Alternatives
We may instead just run multiply instances of the nodejs server, or just decide that the server render in SSR stage only a fixed language.
Is there already any mechanism to fix it? I'm using Sapper BTW.
The text was updated successfully, but these errors were encountered: