Skip to content

Svelte

Importimport { adapter } from "@wuchale/svelte"
Loader extensions.js, .ts, .svelte.js, .svelte.ts
Default filessrc/**/*.svelte, src/**/*.svelte.{js,ts}
CompatibilitySvelte >= 5

Svelte applications may or may not use SvelteKit and that makes the loading of the compiled catalogs a bit different. The Svelte adapter tries to detect whether SvelteKit is in use and write the suitable default loader. Assuming it was correct, follow the following steps. If it was wrong, edit the loader file first.

You can use one of the two approaches mentioned in placeholders.

src/App.svelte
<script>
import { loadLocale } from 'wuchale/load-utils'
// so that the loaders are registered first
import './locales/loader.svelte.js'
// you can use any state from anywhere for the locale
let locale = $state('en')
</script>
{#await loadLocale(locale)}
<!-- Ignored because it is rendered before the catalog is loaded -->
<!-- @wc-ignore -->
Loading translations...
{:then}
<!-- Your app content -->
{/await}

If you use SvelteKit for just a client only app, you can use the above setup for Svelte. This is for when you need SSR.

The SvelteKit setup has two layers (server and client) and the loader file has to account for both. The default loader can be seen as an example.

When rendering on the server, the above setup for Svelte can’t work because it uses a global state and using global states on the server is a bad idea, as it can leak information (in our case the current locale) between requests, because requests share the global state. To deal with this, a separate loader file is generated and a separate utility is provided that can isolate the locale state per request. You can set it up inside hooks.server.{js,ts}.

hooks.server.js
import { loadCatalog, loadIDs, key } from './locales/loader.ssr.svelte.js'
import { runWithLocale, loadLocales } from 'wuchale/load-utils/server'
import { locales } from 'virtual:wuchale/locales'
// load at server startup
loadLocales(key, loadIDs, loadCatalog, locales)
/** @type {import('@sveltejs/kit').Handle} */
export const handle = async ({ event, resolve }) => {
const locale = event.url.searchParams.get('locale') ?? 'en'
return await runWithLocale(locale, () => resolve(event))
}

Now, loadLocales loads all catalogs at the server startup and makes them ready. Then, for each request, runWithLocale makes sure that the requests are isolated and able to access their own catalogs based on the locale from the URL.

Once the page is rendered on the server with the correct locale, the client has to load the catalogs also making sure that the messages don’t change or flicker once it takes over. The best place to load the catalogs then is the load function in the top most +layout.{js,ts} file. But it should only run once it’s in the browser (the server is already handled).

src/routes/+layout.js
import { locales } from 'virtual:wuchale/locales'
import { browser } from '$app/environment'
import { loadLocale } from 'wuchale/load-utils'
// so that the loaders are registered
import '../locales/loader.svelte.js'
/** @type {import('./$types').LayoutLoad} */
export const load: LayoutLoad = async ({url}) => {
const locale = url.searchParams.get('locale') ?? 'en'
if (!locales.includes(locale)) {
return
}
if (browser) {
await loadLocale(locale)
}
}

The Svelte adapter only handles .svelte files and .svelte.{js,ts} files. However, you may have translatable strings in +page.{js,ts}, +layout.{js,ts}, etc. If so, you can use the Vanilla adapter for those files by adding it as another adapter and specifying which files to handle.

wuchale.config.js
// ...
adapters: {
main: svelte(),
js: js({
files: [
'src/**/+{page,layout}.{js,ts}',
'src/**/+{page,layout}.server.{js,ts}',
],
})
}
// ...

Learn more about using multiple adapters.

In addition to the default rules, this adapter implements additional restrictions.

  • The string can be in a variable at the top level, but it should be wrapped inside $derived or $derived.by.
  • It should pass the base heuristic from the Vanilla adapter.
  • If the value is inside $inspect() calls, it is ignored.
  • Otherwise, it is extracted.

Examples:

// In $derived or functions
const message = $derived('This is extracted')
const lowercase = $derived('not extracted')
// Force extraction with comment
const forced = $derived(/* @wc-include */ 'force extracted')
<p title={'Extracted'}>{/* @wc-ignore */ 'Ignore this'}</p>

<script module>s and .svelte.{js,ts} files

Section titled “<script module>s and .svelte.{js,ts} files”

Unlike component code, code inside these places only runs once. If you are only developing client only apps, this doesn’t make much difference.

But if you do SSR, you have to make sure that all translatable text is inside function definitions and adjust your usage accordingly:

function foo() {
const msg = 'Here'
}

Because with SSR, startup means server startup, and so if you just use $derived in these places, they will be stuck with the locale of the first request and subsequent requests with different locales may see a flicker until the client takes over. But if you put them inside function definitions, the function gets executed per request and will not have this problem.

For the main configuration, look in the configuration reference.

For the common adapter configuration, look in the common adapter options.

This adapter doesn’t have additional configuration options.