1. 🚀 Installation
Please follow the installation guide. It uses svelte as an example.
2. Setup in Your App
Svelte applications may or may not use SvelteKit and that makes the loading of the compiled catalogs a bit different. wuchale
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.
For SvelteKit (SSR/SSG)
// src/routes/+layout.js
import type { LayoutLoad } from './$types'
import { locales } from 'virtual:wuchale/locales'
import { loadCatalogs } from 'wuchale/run-client'
import { loadIDs, loadCatalog } from '../locales/loader.svelte.js'
export const prerender = true
export const load: LayoutLoad = async ({url}) => {
const locale = url.searchParams.get('locale') ?? 'en'
if (!(locale in locales)) {
return
}
return {
locale,
catalogs: await loadCatalogs(locale, loadIDs, loadCatalog)
}
}
For Svelte (SPA)
<!-- src/App.svelte -->
<script lang="ts">
import { loadLocale } from 'wuchale/run-client'
import Counter from './lib/Counter.svelte'
let locale = $state('en')
</script>
{#await loadLocale(locale)}
<!-- @wc-ignore -->
Loading translations...
{:then}
<!-- Your app content -->
{/await}
🧠 Behavior Explanation (Svelte adapter)
What Gets Extracted?
This is decided by the heuristic function which you can customize. A sensible default heuristic function is provided out of the box. Here’s how it works:
General rule (applies everywhere):
- If the text contains no letters used in any natural language (e.g., just numbers or symbols), it is ignored.
In markup
(<p>Text</p>
):
- All textual content is extracted.
Examples:
<p>This is extracted</p>
<!-- @wc-ignore -->
<p>This is not extracted</p>
In attribute
(<div title="Info">
):
- If the first character is a lowercase English letter (
[a-z]
), it is ignored. - If the element is a
<path>
, it is ignored (e.g., for SVGd="M10 10..."
attributes). - Otherwise, it is extracted.
Examples:
<img alt="Profile Picture" class="not-extracted" />
In script
(<script>
and .svelte.js/ts
):
script
is handled by the ES adapter of the core package with some additional restrictions.
- If it doesn’t pass the base heuristic from the ES adapter, it is ignored.
- If it’s not inside
$derived
or$derived.by
, it is ignored. - 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>
If you need more control, you can supply your own heuristic function in the configuration. Custom heuristics can return undefined
or null
to fall back to the default. For convenience, the default heuristic is exported by the package.
💡 You can override extraction with comment directives:
@wc-ignore
— skips extraction@wc-include
— forces extraction
These always take precedence.
🛠️ Configuration Reference
For the main plugin configuration, look in Usage.
import { adapter as svelte } from "@wuchale/svelte"
const svelteAdapter = {
// Where to store translation files. {locale} will be replaced with the respective locale.
catalog: './src/locales/{locale}',
// Files to scan for translations
// You can technically specify non svelte js/ts files, but they would not be reactive
files: ['src/**/*.svelte', 'src/**/*.svelte.{js,ts}'],
// Custom extraction logic
// signature should be: (text: string, details: object) => boolean | undefined
// details has the following properties:
// scope: "markup" | "attribute" | "script",
// declaring?: "variable" | "function" | "expression",
// insideFuncDef?: boolean,
// topLevelCall?: string,
// call?: string,
// element?: string,
// attribute?: string,
// file?: string,
heuristic: defaultHeuristic,
// Whether to split the compiled catalogs into even smaller files
granularLoad: false,
// In some cases, avoiding async loading and directly importing the
// catalogs by the code that uses them may be desired. This is how Paraglide
// works. However, it is not recommended as all catalogs then get bundled with
// the code that uses them even though only one is required by the user. This
// can inflate the bundle side. But if this is desired anyway, it can be
// enabled here.
bundleLoad: false
// When using granularLoad, generate a load ID for each file. The ID should
// be like a keyword, only [a-zA-Z0-9_] are allowed. You can return the same
// ID to group compiled catalogs to prevent too much splitting
generateLoadID: defaultGenerateLoadID,
// Write content that would be virtual to disk
writeFiles: {
// the compiled catalogs
compiled: false,
// the catalogs proxy
proxy: false,
// the transformed code
transformed: false,
// Output directory for the transformed code.
outDir: 'src/locales/.output'
},
// Your plural function name
pluralFunc: 'plural',
}