Svelte Phrase Chain

A powerful, type-safe internationalization (i18n) library for Svelte applications. Built with Svelte 5 runes and TypeScript for a modern, reactive localization experience.

๐Ÿ› ๏ธ CLI Quickstart

Svelte Phrase Chain uses a dedicated CLI for project initialization, ensuring a streamlined setup process.

1. Install via CLI

Note: You can skip installing the package, as the CLI will effectively just copy paste all necessary files to your project.

# Using bun
bunx svelte-phrase-chain init

# Using npm
npx svelte-phrase-chain init

# Using pnpm
pnpx  svelte-phrase-chain init

The CLI accepts several options to customize your setup:

# Full example with all options
bunx svelte-phrase-chain init \
  --locales en,fr,es,de \
  --fallbackLocale en \
  --persistLocale \
  --localStorageKey app_locale \
  --translationsDir src/lib/i18n/translations \
  --generateTranslations \
  --debug
Option Description Default
--locales Supported locales (comma-separated) en,es,fr
--fallbackLocale Fallback locale en
--persistLocale Persist locale in localStorage true
--localStorageKey LocalStorage key for locale app_locale
--translationsDir Translations folder path src/lib/translations
--generateTranslations Generate initial translation JSON files true
--debug Enable debug logging true

2. Generated files

The CLI will generate:

src/
โ””โ”€โ”€ lib/
    โ”œโ”€โ”€ i18n/
    โ”‚   โ”œโ”€โ”€ i18n.ts              # Main i18n configuration & exports
    โ”‚   โ””โ”€โ”€ core/                # Core implementation files
    โ”‚       โ”œโ”€โ”€ index.svelte.ts  # Main i18n functionality
    โ”‚       โ””โ”€โ”€ types.ts         # TypeScript types
    โ””โ”€โ”€ translations/            # Translation JSON files
        โ”œโ”€โ”€ en.json
        โ”œโ”€โ”€ es.json
        โ””โ”€โ”€ fr.json

3. Import and use i18n in your components

<script lang="ts">
  import { t, setLocale, locale, initLocale } from '$lib/i18n/i18n';
  import type { Locale } from '$lib/i18n/i18n';
  
  // Initialize with browser detection
  initLocale({ 
    preferBrowser: true, 
    preferStorage: true,
    defaultLocale: 'en'
  });
  
  // Example usage
  let name = $state("User");
  let messageCount = $state(1);
  
  function changeLanguage(lang: Locale) {
    setLocale(lang);
  }
</script>

<h1>{t('common.welcome')}</h1>
<p>{t('common.greeting', { name })}</p>

<p>{t('user.messageCount', { count: messageCount }, messageCount)}</p>

<!-- Language switcher -->
<div>
  <p>Current locale: {locale()}</p>
  <button onclick={() => changeLanguage('en')}>English</button>
  <button onclick={() => changeLanguage('fr')}>Franรงais</button>
  <button onclick={() => changeLanguage('es')}>Espaรฑol</button>
</div>

Philosophy

Svelte Phrase Chain follows the "code is yours, do what you want with it" approach, similar to shadcn and other projects. It is designed with sensible defaults, but all you need to add is up to you:

  • Want to support multiple English versions (en-gb, en-us, en-au)? Feel free to add it!
  • Don't like how plurals are handled? Feel free to rewrite it!
  • Need to customize anything? The code is yours to modify.

How CLI Affects Your Code

When you run the CLI:

  • Yes, it will overwrite code if you run it again in an existing project
  • But as you own the code, you can validate the changes via your source control of choice
  • Add and merge changes you want (especially if you've customized it to fit your needs)
  • All generated code belongs to your project - modify it freely to match your requirements

Why Svelte Phrase Chain?

While there are several i18n solutions for Svelte like svelte-i18n and typesafe-i18n, Svelte Phrase Chain offers distinct advantages:

  • Built for Svelte 5 - Leverages runes for true reactivity without stores or contexts
  • Fully Type-Safe - TypeScript-first approach with automatic type generation from translation files
  • Modern API - Clean, intuitive API designed for the latest Svelte development practices
  • Zero External Dependencies - Core functionality has no runtime dependencies beyond Svelte itself
  • Fine-Grained Reactivity - Updates only what changes, optimized for Svelte's rendering model
  • Extensible by Design - Built with customization and extension in mind

Features

  • ๐Ÿ”„ Reactive - Built with Svelte 5 runes for automatic reactivity
  • ๐Ÿ” Type-safe - Full TypeScript support with auto-completion for translation keys
  • ๐ŸŒ Dynamic locale switching - Change languages on-the-fly
  • ๐Ÿ“ฆ Lazy loading - Load translation files on demand based on the active locale
  • ๐Ÿ“Š Rich formatting options:
    • ๐Ÿ“ Parameter interpolation (Hello, {name}!)
    • ๐Ÿ”ข Pluralization with count-aware formatting
    • ๐Ÿ“… Date formatting with relative time support
    • ๐Ÿงฎ Number formatting with currency and percent support
  • ๐Ÿงช Schema validation - Zod-powered schema validation for translation files

Translation File Structure

The library expects JSON files with translations for each locale:

// src/lib/translations/en.json
{
  "common": {
    "welcome": "Welcome to our app",
    "greeting": "Hello, {name}!",
    "footer": "ยฉ 2025 Awesome App. All rights reserved."
  },
  "user": {
    "messageCount": {
      "zero": "No messages",
      "one": "You have {count} unread message",
      "other": "You have {count} unread messages"
    }
  }
}

Advanced Usage

Date Formatting

<script>
  import { t } from '$lib/i18n/i18n';
  
  const joinDate = new Date('2023-01-15');
  const lastLoginDate = new Date(Date.now() - 3600000); // 1 hour ago
</script>

<!-- In your translation file: "user.joinDate": "Member since {date:date}" -->
<p>{t('user.joinDate', { date: joinDate })}</p>

<!-- In your translation file: "user.lastLogin": "Last login: {date:relative}" -->
<p>{t('user.lastLogin', { date: lastLoginDate })}</p>

Pluralization

<script>
  import { t } from '$lib/i18n/i18n';
  
  let count = $state(1);
  
  function increment() {
    count++;
  }
</script>

<!--
Translation structure in user.messageCount:
{
  "zero": "No messages",
  "one": "{count} message",
  "other": "{count} messages"
}
-->

<p>{t('user.messageCount', { count }, count)}</p>
<button on:click={increment}>Add Message</button>

Schema Validation

Use the provided schema tools to validate your translation files:

// scripts/validate-translations.ts
import { createI18nSchema } from 'svelte-phrase-chain/schema';
import en from '../src/lib/translations/en.json';
import es from '../src/lib/translations/es.json';
import fr from '../src/lib/translations/fr.json';

const mySchema = createI18nSchema({
  pluralKeyIdentifier: (key) => key.endsWith('Count'),
  requiredPluralKeys: ['one', 'other'],
  optionalPluralKeys: ['zero', 'few', 'many'],
  allowedDateFormats: ['date', 'relative', 'fullDate']
});

// Validate all translation files
try {
  const enValid = mySchema.parse(en);
  const esValid = mySchema.parse(es);
  const frValid = mySchema.parse(fr);
  console.log("โœ… All translation files are valid!");
} catch (error) {
  console.error("โŒ Validation failed:", error);
  process.exit(1);
}

Browser Compatibility

Supports all modern browsers with ECMAScript 2015 (ES6) support, with features relying on:

  • Intl API for formatting
  • Dynamic imports for lazy loading

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with Svelte 5 and TypeScript
  • Inspired by modern i18n libraries like react-intl and i18next

Top categories

svelte logo

Need a Svelte website built?

Hire a professional Svelte developer today.
Loading Svelte Themes