@les3dev/i18n
v0.1.0
Published
Type-safe internationalization library for JavaScript and Svelte 5 with full TypeScript support.
Readme
@les3dev/i18n
Type-safe internationalization library for JavaScript and Svelte 5 with full TypeScript support.

Features
- Type-safe translations — Full TypeScript inference for translation keys and arguments
- Parameterized translations — Support for functions with typed arguments
- Framework agnostic — Works with any JavaScript project, with optional Svelte 5 integration
- Automatic locale detection — Built-in Accept-Language header parsing
- Lightweight — No dependencies, small bundle size
Installation
npm install @les3dev/i18nFor Svelte 5 support:
npm install @les3dev/i18n svelte@^5Basic Usage (JavaScript/TypeScript)
1. Define Your Translations
Create translation files for each locale:
// translations/fr.ts
import type {DefaultTranslation} from "@les3dev/i18n";
export default {
"Bienvenue !": "Bienvenue !",
Commencer: "Commencer",
"Bonjour {name}": (name: string) => `Bonjour ${name}`,
"Ça marche aussi en JS !": "Ça marche aussi en JS !",
} satisfies DefaultTranslation;// translations/en.ts
import type {Translation} from "..";
export default {
"Bienvenue !": "Welcome!",
Commencer: "Start",
"Bonjour {name}": (name: string) => `Hello ${name}`,
"Ça marche aussi en JS !": "Also works in js!",
} satisfies Translation;2. Register Translations
import {register_translations} from "@les3dev/i18n";
import fr from "./translations/fr";
import en from "./translations/en";
export const i18n = register_translations({fr, en}, "fr");
export const {translate} = i18n;
export type Locale = Parameters<typeof translate>[0];
export type Translation = typeof fr;3. Automatic Locale Detection
import {get_locale_from_headers} from "@les3dev/i18n";
// Detect locale from Accept-Language header
const locale = get_locale_from_headers(request.headers, i18n);
const greeting = i18n.translate(locale, "greeting");Svelte 5 Integration
For Svelte 5 projects, use the create_i18n_context helper to manage translations reactively.
See sveltekit-example for a fully working example
1. Create Context File
// src/lib/i18n/context.svelte.ts
import {create_i18n_context} from "@les3dev/i18n/svelte";
import {i18n} from "./index";
export const {get_i18n_context, set_i18n_context} = create_i18n_context(i18n);2. Initialize in Layout
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { set_i18n_context } from "$lib/i18n/context.svelte";
let { children } = $props();
// Initialize with locale from server (cookie, URL, etc.)
set_i18n_context(() => data.locale);
</script>
{@render children()}3. Use in Components
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { get_i18n_context } from "$lib/i18n/context.svelte";
const i18n = get_i18n_context();
</script>
<h1>{i18n.t("greeting")}</h1>
<p>{i18n.t("greet", "Alice")}</p>
<!-- Change locale -->
<select bind:value={i18n.locale}>
<option value="fr">Français</option>
<option value="en">English</option>
</select>SvelteKit Full Example
Project Structure
src/
├── lib/
│ └── i18n/
│ ├── index.ts # register_translations setup
│ ├── context.svelte.ts # Svelte context helpers
│ └── translations/
│ ├── fr.ts
│ └── en.ts
├── routes/
│ ├── +layout.svelte
│ ├── +layout.server.ts # Pass locale to client
│ ├── +page.svelte
│ └── +page.server.ts
├── hooks.server.ts # Locale detection
└── app.d.ts # Type declarationsSetup Files
// src/lib/i18n/index.ts
import {register_translations} from "@les3dev/i18n";
import fr from "./translations/fr";
import en from "./translations/en";
export const i18n = register_translations({fr, en}, "fr");
export const {translate, locales, default_locale} = i18n;
export type Locale = Parameters<typeof translate>[0];// src/lib/i18n/context.svelte.ts
import {create_i18n_context} from "@les3dev/i18n/svelte";
import {i18n} from "./index";
export const {get_i18n_context, set_i18n_context} = create_i18n_context(i18n);// src/app.d.ts
import type {TranslateFn} from "@les3dev/i18n";
import {i18n} from "$lib/i18n";
declare global {
namespace App {
interface Locals {
locale: "fr" | "en";
translate: TranslateFn<typeof i18n>;
}
}
}
export {};// src/hooks.server.ts
import {i18n} from "$lib/i18n";
import {get_locale_from_headers} from "@les3dev/i18n";
import type {Handle} from "@sveltejs/kit";
export const handle: Handle = async ({event, resolve}) => {
// Check cookie first, then Accept-Language header
const cookieLocale = event.cookies.get("locale");
if (cookieLocale && i18n.locales.includes(cookieLocale as (typeof i18n.locales)[number])) {
event.locals.locale = cookieLocale as "fr" | "en";
} else {
event.locals.locale = get_locale_from_headers(event.request.headers, i18n);
}
event.locals.translate = (key, ...args) => i18n.translate(event.locals.locale, key, ...args);
return resolve(event);
};// src/routes/+layout.server.ts
import type {LayoutServerLoad} from "./$types";
export const load: LayoutServerLoad = ({locals}) => {
return {
locale: locals.locale,
};
};<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { set_i18n_context } from "$lib/i18n/context.svelte";
let { data, children } = $props();
set_i18n_context(() => data.locale);
</script>
{@render children()}<!-- src/routes/+page.svelte -->
<script lang="ts">
import { get_i18n_context } from "$lib/i18n/context.svelte";
const i18n = get_i18n_context();
</script>
<h1>{i18n.t("welcome")}</h1>
<p>{i18n.t("greet", "World")}</p>// src/routes/+page.server.ts
import type {PageServerLoad, Actions} from "./$types";
import {redirect} from "@sveltejs/kit";
export const load: PageServerLoad = ({locals}) => {
return {
greeting: locals.translate("greeting"),
};
};
export const actions: Actions = {
setLocale: async ({request, cookies}) => {
const formData = await request.formData();
const locale = formData.get("locale") as string;
cookies.set("locale", locale, {
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
redirect(303, request.url);
},
};<!-- src/routes/+page.svelte -->
<script lang="ts">
import { get_i18n_context } from "$lib/i18n/context.svelte";
let { data } = $props();
const i18n = get_i18n_context();
</script>
<h1>{i18n.t("welcome")}</h1>
<p>{i18n.t("greet", "World")}</p>
<form method="POST" action="?/setLocale">
<select name="locale" onchange={(e) => {
const form = e.currentTarget.form;
if (form) form.requestSubmit();
}}>
<option value="fr" selected={i18n.locale === "fr"}>Français</option>
<option value="en" selected={i18n.locale === "en"}>English</option>
</select>
</form>Type Safety
The library provides full TypeScript inference:
// Translation keys are inferred
i18n.translate("en", "greeting"); // OK
i18n.translate("en", "invalid"); // Error: doesn't exist
// Function arguments are type-checked
i18n.translate("en", "greet"); // Error: missing argument
i18n.translate("en", "greet", "Alice"); // OK
i18n.translate("en", "greet", 123); // Error: wrong typeLicense
MIT
