Svelte langsys-js-svelte

A thin Svelte 5 binding over langsys-js-typescript — the mature, canonical binding. The base signals already satisfy Svelte's store contract, so translations read with $-store syntax.

Watch · 1:00 Svelte: the walkthrough
Using an AI coding agent? Give it this prompt
Add Langsys translation to this Svelte app.

1. Read https://docs.langsys.dev/llms.txt, then follow
   https://docs.langsys.dev/learn/sdk/svelte/ for the install and setup.
2. The English text in the code is the translation key. Wrap user-facing
   strings as they are: no keys file, no extraction step, and no
   hand-written plural or gender branches (Langsys generates those).
3. Read the Langsys project id and API key from environment variables.
   The write key is for development and servers only; anything shipped
   to the browser gets the read-only key.
4. Keep brand names and user-generated content out of translation.

Install

npm install langsys-js-svelte

No project or keys yet? The Quickstart gets you both in two minutes.

Initialize

import { writable } from 'svelte/store';
import { LangsysApp } from 'langsys-js-svelte';

const locale = writable('en-US'); // a standard Svelte writable

await LangsysApp.init({
  projectid: import.meta.env.VITE_LANGSYS_PROJECT_ID,
  key: import.meta.env.VITE_LANGSYS_API_KEY, // read-only in the browser
  UserLocaleStore: locale,
});

Interactive playground

Watch · 1:09 t(phrase, category, params)

Edit the phrase and category, set the params as fields or raw JSON, then switch locale and watch the real SDK translate live.

Examples
Params
Output

Hello, Sarah! You have 3 new messages.

const value = $derived($t("Hello, {name}! You have {count} new messages.", "Greetings", {"name":"Sarah","count":3}));
t() — inline string, in a component View source ↗
<script>
  import { t } from 'langsys-js-svelte';
  let name = 'Sarah';
</script>

<p>{$t('Hello, {name}!', 'Greetings', { name })}</p>

Which one do I use?

You’re translating… Use Why
A plain string in your code — a label, a title, a message. Params included; write counts flat and Langsys generates the plural forms. $t Returns a string. The phrase in your code is both the lookup key and the base-language default.
One sentence containing inline markup or dynamic values, kept whole. <Phrase> Inline elements become neutral tokens, so translators reorder the full sentence — markup position included — and the real elements are reconstituted at render.
A region of markup with several phrases — a hero, a footer, a card. <Translate> One tag registers the region as a single content block: every text run is extracted as its own phrase and the markup structure is preserved.
Something that must never translate — brand names, code, IDs. <DontTranslate> The tokenizer and renderer skip the subtree; it stays verbatim while the sentence around it translates.

Rule of thumb: string → $t · sentence with markup → <Phrase> · region of markup → <Translate> · untouchable → <DontTranslate>.

Each content component rendered live on the left; the code that produces it on the right — and its demo cut above the pair.

Watch · 1:04 <Translate>

<Translate> — content block

Welcome to our store

Browse the catalog in your language.

<Translate> — content block View source ↗
<script>
  import { Translate } from 'langsys-js-svelte';
</script>

<Translate category="Home">
  <h3>Welcome to our store</h3>
  <p>Browse the catalog in your language.</p>
</Translate>
Watch · 1:06 <Phrase>

<Phrase> — params & markup

Hi %name%, you have %count% items in your cart.
<Phrase> — params & markup (%name%) View source ↗
<Phrase category="Cart" params={{ name: 'Sarah', count: 3 }}>
  Hi %name%, you have %count% items in your cart.
</Phrase>
Watch · 0:34 <DontTranslate>

<DontTranslate> — never translated

Welcome! This sentence always stays in English. Thanks for visiting!

<DontTranslate> View source ↗
<!-- Inside a translated region, the marked span alone stays verbatim. -->
<Translate category="Tour">
  <p>Welcome! <DontTranslate>This sentence always stays in English.</DontTranslate> Thanks for visiting!</p>
</Translate>

Gender agreement

Watch · 0:39 Gender agreement

Spanish, French, or Russian inflect {username} has been invited for the person it’s about. Langsys generates those branches during translation — the same way it generates plurals — and the argument is named after the variable, with _gender appended: male, female, or other. Pass it whenever you know it. How it works, and how to find the phrases that need it →

Passing the gender argument
<script>
  import { t } from 'langsys-js-svelte';
  export let user; // user.gender is 'male' | 'female' | 'other'
</script>

<!-- Pass the agreement argument alongside the value it inflects. -->
<p>{$t('{username} has been invited', 'Team',
    { username: user.name, username_gender: user.gender })}</p>

Don’t know the gender? Pass 'other' explicitly — it’s the neutral branch. If it is missing anyway, the SDK takes the other branch for you (langsys-js-typescript 0.6.4 and later), so the sentence still reads correctly.

Content blocks

Watch · 0:42 Content blocks

A <Translate> region registers as one content block: the section is translated and reviewed as a unit in the Translation Manager, but every text run inside it is still its own phrase. Write the same sentence somewhere else and it reuses the translation it already has.

One block, three phrases
<Translate category="Home">
    <h3>Welcome to our store</h3>
    <p>Browse the catalog in your language.</p>
    <p>Free shipping on orders over $50.</p>
</Translate>

Categories

Watch · 0:34 Categories

Other i18n libraries make you invent a key for every string because one word can mean two things. In Langsys the sentence stays in your code and the category does that job: the phrase plus its category is the key, so the same word in two categories becomes two translations.

Same word, two meanings
<!-- The phrase plus its category is the key. -->
<a href="/">{$t('Home', 'Main menu')}</a>          <!-- es: Inicio -->
<h2>{$t('Home', 'Home repairs')}</h2>           <!-- es: Hogar -->

Attributes

Watch · 1:07 Attributes

A form is only half translated if the visible text changes and the placeholder, the image description and the screen-reader label stay in English. <Translate> reaches them in the same pass: placeholder, alt, title, aria-label and aria-placeholder are translated along with the text, with no extra markup. Anything marked translate="no" is skipped, children included.

Text and attributes, one tag
<Translate category="Checkout">
    <label>Email address</label>
    <input placeholder="[email protected]" />
    <img src="/lock.svg" alt="Secure checkout" />
    <button aria-label="Place your order">
        Place order
    </button>
</Translate>

Numbers & dates

Watch · 0:46 Numbers & dates

Pass numbers and dates as params and the SDK formats them for the locale being shown, following CLDR (the Unicode locale data): a number goes through Intl.NumberFormat and a Date through Intl.DateTimeFormat. That covers more than the separator: grouping is a shape, so 1234567 is 1,234,567 in US English and 12,34,567 in Indian English (en-IN).

The flip side: anything passed as a number is treated as a quantity. An order number isn't one, so pass it as a string, or German shows your order reference as 1.048.576.

Formatted per locale
<script lang="ts">
    import { t } from 'langsys-js-svelte';
    const renewsAt = new Date(2026, 2, 14); // 14 March 2026, local time
</script>

<p>{$t('Balance: {amount}', 'Billing', { amount: 1234.5 })}</p>
<p>{$t('Renews {when}', 'Billing', { when: renewsAt })}</p>

<!-- An order number is not a quantity: pass it as a string. -->
<p>{$t('Order {ref}', 'Billing', { ref: '1048576' })}</p>

Server-side rendering

Fetch the catalog on the server (SvelteKit) and hand it to the client, so the SDK starts without fetching it again. The SDK translates in the browser, so the server-rendered HTML is in the base language until it starts.

// SvelteKit: +layout.server.ts seeds the catalog, the client init reuses it.
LangsysApp.init({
  projectid, key,
  UserLocaleStore: locale,
  initialTranslations,        // fetched on the server
  initialTranslationsLocale,  // fetched on the server
  ssrTokenStrategy: 'client',
});
Search the docs & Learning Center