From Greek "παράγλωσσος" (paraglossus) - multilingual, speaking many languages
Modern internationalization (i18n) library for SvelteKit with Svelte 5 Runes and remotes support.
$derived)command()nav.home, button.save){{name}}, {{count}})npm install paragone
pnpm add paragone
bun add paragone
Create or update src/hooks.server.ts:
import type { Handle } from '@sveltejs/kit';
import { getLanguage } from 'paragone';
export const handle: Handle = async ({ event, resolve }) => {
const language = getLanguage(
event.cookies,
event.request.headers.get('accept-language')
);
event.locals.language = language;
return resolve(event);
};
Update src/app.d.ts:
declare global {
namespace App {
interface Locals {
language: string;
}
}
}
export {};
Create src/routes/+page/locale.json:
{
"en": {
"title": "Welcome",
"greeting": "Hello, {{name}}!",
"button": {
"login": "Login",
"signup": "Sign up"
}
},
"de": {
"title": "Willkommen",
"greeting": "Hallo, {{name}}!",
"button": {
"login": "Anmelden",
"signup": "Registrieren"
}
}
}
src/routes/+page.server.ts:
import type { PageServerLoad } from './$types';
import { I18n } from 'paragone';
import * as locale from './locale.json';
export const load: PageServerLoad = async ({ locals }) => {
const { t } = new I18n(locale, locals.language);
return {
title: t('title'),
language: locals.language
};
};
src/routes/+page.svelte:
<script lang="ts">
import { I18n } from 'paragone';
import * as locale from './locale.json';
let { data } = $props();
const { t } = $derived(new I18n(locale, data.language));
</script>
<h1>{t('title')}</h1>
<p>{t('greeting', { name: 'Sarah' })}</p>
<button>{t('button.login')}</button>
<button>{t('button.signup')}</button>
but you can also use it in a form remote function or action...:
First, create src/lib/remotes/common.remote.ts:
import { command, getRequestEvent } from "$app/server";
import { setLanguage } from "paragone";
import z from "zod";
/**
* Remote function to change the user's language preference
* @example await changeLanguage('de')
*/
export const changeLanguage = command(
z.string().min(2).max(10),
async (language) => {
const event = getRequestEvent();
setLanguage(event.cookies, language);
return { success: true, language };
}
);
Then use it in src/routes/+page.svelte:
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { changeLanguage } from '$lib/remotes/common.remote';
async function switchLanguage(lang: string) {
await changeLanguage(lang);
await invalidateAll();
}
</script>
<button onclick={() => switchLanguage('en')}>English</button>
<button onclick={() => switchLanguage('de')}>Deutsch</button>
Note:
changeLanguageis not exported fromparagonebecause it uses$app/serverwhich only works in the context of your SvelteKit project. See docs/REMOTE_FUNCTIONS.md for alternative implementations (Form Actions, API Routes, etc.).
I18n Classconst i18n = new I18n(translations, locale);
translations: Object with language keys containing translation objectslocale: Current language code (e.g., 'en', 'de')t(key: string, vars?: Record<string, string | number>): stringGet translation by key with optional variable replacement.
t('title') // "Welcome"
t('greeting', { name: 'Mike' }) // "Hello, Mike!"
t('button.login') // "Login" (nested keys)
has(key: string): booleanCheck if translation key exists.
if (i18n.has('optional.key')) {
console.log(t('optional.key'));
console.log(t('optional.key'));
}
getLocale(): stringGet current locale.
const locale = i18n.getLocale(); // "en" or "de"
getLanguage(cookies: Cookies, acceptLanguage: string | null): stringGet language from cookie or detect from browser's Accept-Language header.
import { getLanguage } from 'paragone';
const language = getLanguage(
event.cookies,
event.request.headers.get('accept-language')
);
setLanguage(cookies: Cookies, language: string): voidSet language to cookie.
import { setLanguage } from 'paragone';
setLanguage(cookies, 'de');
changeLanguage(language: string)Change user's language preference. You need to implement this in your own project because it uses SvelteKit's command() which cannot be exported from a library.
Create src/lib/remotes/common.remote.ts:
import { command, getRequestEvent } from "$app/server";
import { setLanguage } from "paragone";
import z from "zod";
export const changeLanguage = command(
z.string().min(2).max(10),
async (language) => {
const event = getRequestEvent();
setLanguage(event.cookies, language);
return { success: true, language };
}
);
Usage:
import { changeLanguage } from '$lib/remotes/common.remote.ts';
await changeLanguage('de');
await invalidateAll(); // Reload page data
See docs/REMOTE_FUNCTIONS.md for alternative implementations (Form Actions, API Routes).
For app-wide translations (navigation, common buttons, etc.):
src/lib/i18n/locale.json:
{
"en": {
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
}
},
"de": {
"nav": {
"home": "Startseite",
"about": "Über uns",
"contact": "Kontakt"
}
}
}
Use in layout:
import * as globalLocale from '$lib/i18n/locale.json';
const { t } = new I18n(globalLocale, locals.language);
import * as globalLocale from '$lib/i18n/locale.json';
import * as pageLocale from './locale.json';
const combined = {
en: { ...globalLocale.en, ...pageLocale.en },
de: { ...globalLocale.de, ...pageLocale.de }
};
const { t } = new I18n(combined, locals.language);
import { command } from '$app/server';
import { I18n } from 'paragone';
import * as locale from './locale.json';
import z from 'zod';
export const saveData = command(z.object({ name: z.string() }), async (data, { locals }) => {
const { t } = new I18n(locale, locals.language);
// ... your logic
return {
success: true,
message: t('messages.saved')
};
});
Note: Remote functions must be defined in your project, not imported from paragone.
Edit the constants in your local copy if needed:
const COOKIE_NAME = 'language';
const DEFAULT_LANGUAGE = 'en';
const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es'];
Cookie settings:
{
path: '/',
maxAge: 60 * 60 * 24 * 365, // 1 year
sameSite: 'lax',
httpOnly: false
}
The system automatically detects the user's browser language on first visit:
language cookie existsAccept-Language headeren)Example header: Accept-Language: de-DE,de;q=0.9,en;q=0.8
→ Returns de (if supported)
Check out the examples folder for complete working examples:
npm test
$derived in components for reactivityinvalidateAll() after language changelanguage from server to client1. Request comes in
↓
2. hooks.server.ts calls getLanguage()
↓
3. Checks: Cookie → Browser → Default ('en')
↓
4. Stores in locals.language
↓
5. Page loads with language
↓
6. User clicks language button
↓
7. changeLanguage() sets cookie
↓
8. invalidateAll() reloads with new language
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)MIT © Tayfun Guelcan
Made for the Svelte community
And by the way For those who think, “Oh no, not another language library!”, the answer is: Shut up and use it!