Elegant, type-safe and SSR-friendly internationalization for your edges-svelte app.
This package provides a powerful translation provider with:
✅ Full SSR support
✅ Client-side lazy switching
✅ Cookie-based locale persistence
✅ Automatic <html lang>
updates
✅ Pluralization and variable interpolation
✅ Type-safe keys
✅ Simple integration with edges-svelte
This plugin requires edges-svelte
to work properly.
Install both packages:
npm install edges-svelte edges-svelte-translations
or if you already use edges-svelte
just
npm install edges-svelte-translations
Export your translation modules:
// $lib/translations/messages/index.ts or whereever you want
export const messages = {
en: () => import('./en.js').then((res) => res.default),
ru: () => import('./ru.js').then((res) => res.default)
};
Each translation file (e.g., en.js
, ru.js
) should export a default object containing the translations.
Example translation files:
English (en.js
):
export default {
someText: 'some text',
testVars: 'Test variable: {{someVar}}',
testPlural: 'You have {{ count | plural: item, items }}',
home: {
routeName: 'Home'
}
};
Russian (ru.js
):
export default {
someText: 'некий текст',
testVars: 'Тестовая переменная: {{someVar}}',
testPlural: 'У вас {{ count | plural: предмет, предмета, предметов }}',
home: {
routeName: 'Главная'
}
};
Initialize the translation provider:
// $lib/translations/index.ts or whereever you want
import { messages } from './messages';
import { createTranslations } from 'edges-svelte-translations';
export const TranslationProvider = createTranslations({
messages,
initLang: 'en',
initLangFromAcceptLanguage: true,
cookieName: 'lang'
});
In src/hooks.server.ts
, configure the edge middleware:
// src/hooks.server.ts
import { dev } from '$app/environment';
import { edgesHandle } from 'edges-svelte/server';
import { type Handle } from '@sveltejs/kit';
import { TranslationProvider } from '$lib/translations';
export const handle: Handle = async ({ event, resolve }) => {
return edgesHandle(
event,
async ({ edgesEvent, serialize }) => {
const { preloadTranslation, applyHtmlLocaleAttr } = TranslationProvider();
await preloadTranslation(edgesEvent);
return resolve(edgesEvent, {
transformPageChunk: ({ html }) => {
const serialized = serialize(html);
return applyHtmlLocaleAttr(serialized);
}
});
},
dev
);
};
In src/routes/+layout.server.ts
in locals, the server provides the detected lang
and translations
. There are two synchronization strategies:
Pass only { lang }
and false
to syncTranslation
, causing the client to reimport translations:
// +layout.server.ts
export const load: LayoutServerLoad = async ({ locals }) => {
return { lang: locals.lang };
};
// +layout.ts
export const load: LayoutLoad = async ({ data }) => {
const { syncTranslation } = TranslationProvider();
if (browser) {
await syncTranslation({ lang: data.lang }, false);
}
};
✅ Tiny initial payload
⚠️ Client makes an additional import of current translations
Pass { lang, translations }
and optionally true
, syncing the client directly with server translations:
// +layout.server.ts
export const load: LayoutServerLoad = async ({ locals }) => {
return { lang: locals.lang, translations: locals.translations };
};
// +layout.ts
export const load: LayoutLoad = async ({ data }) => {
const { syncTranslation } = TranslationProvider();
if (browser) {
await syncTranslation({ lang: data.lang, translations: data.translations });
}
};
✅ Instantly available client-side translations
⚠️ Increases page.data
size and HTML payload
Strategy | HTML Size | Client reimport |
---|---|---|
Option A | ✅ Small | ❌ Yes |
Option B | ❌ Large | ✅ No |
In your Svelte components, use the translation provider:
<script lang="ts">
import { TranslationProvider } from '../lib/translation/index.js';
const { t, locale, switchLocale } = TranslationProvider();
</script>
<p>{$t('testVars', { someVar: 555 })}</p>
<p>3 - {$t('testPlural', { count: 3 })}</p>
<p>1 - {$t('testPlural', { count: 1 })}</p>
<p>5 - {$t('testPlural', { count: 5 })}</p>
<button onclick={() => switchLocale($locale === 'ru' ? 'en' : 'ru')}>
Current lang - {$locale}. Switch
</button>
You can set cookieName
to use and sync plugin current language with cookies automatically:
// src/lib/translation/index.ts
import { messages } from './locales/index.js';
import { createTranslations } from 'edges-svelte';
export const TranslationProvider = createTranslations({
messages,
initLang: 'ru',
cookieName: 'lang'
});
createTranslations(options)
Option | Description | Required |
---|---|---|
messages |
An object where each locale maps to an async import function. | ✅ |
initLang |
Fallback language. | ✅ |
cookieName |
Cookie key for saving user language. | Optional |
initLangFromAcceptLanguage |
If true, auto-detects language from Accept-Language . |
Optional |
Method | Description |
---|---|
preloadTranslation(event) |
Loads translations on server. |
syncTranslation(data) |
Syncs server data on client. |
switchLocale(locale) |
Switches language on client. |
t(key, vars?) |
Translation function. |
locale |
Reactive store with current locale. |
applyHtmlLocaleAttr(html) |
Replace %lang% in rendered HTML with current language. |
subscribeLocaleChangeEvent(cb) |
Listen to language changes. |
This package has built-in pluralization for both 2-form (English) and 3-form (Slavic languages) rules.
Syntax:
Use | plural: one, few, many
inside your strings:
{
"cart_items": "You have {{ count | plural: item, items }}.",
"cart_items_ru": "У вас {{ count }} {{ count | plural: товар, товара, товаров }}."
}
How it works:
For 2-form languages (like English):
item
→ singular (1
)items
→ plural (0, 2, 3, ...
)For 3-form languages (like Russian, Ukrainian):
товар
→ singular (1
)товара
→ few (2, 3, 4
)товаров
→ many (0, 5, 6, ...
)If you pass a variable that is not a number, it logs a warning and returns an empty string.
Crafted with ❤️ by Pixel1917.