Roxabi Boilerplate
Guides

Internationalization (i18n)

Guide to using Paraglide JS for internationalization in Roxabi Boilerplate

Overview

Roxabi Boilerplate uses Paraglide JS for internationalization. Paraglide compiles translations into tree-shakeable JavaScript functions, resulting in up to 70% smaller i18n bundle sizes compared to traditional libraries.

Key Benefits

  • Tree-shakeable: Only used translations are bundled
  • Type-safe: Full TypeScript support with autocomplete
  • Fast: No runtime parsing, messages are pre-compiled
  • Simple: No complex configuration or context providers

Project Structure

apps/web/
├── messages/              # Source translation files
│   ├── en.json           # English (base locale)
│   └── fr.json           # French
├── project.inlang/        # Paraglide configuration
│   └── settings.json
└── src/paraglide/         # Auto-generated (do not edit)
    ├── messages.js        # Message functions
    └── runtime.js         # Locale utilities

Configuration

The Paraglide Vite plugin is configured in apps/web/vite.config.ts:

paraglideVitePlugin({
  project: './project.inlang',
  outdir: './src/paraglide',
  strategy: ['cookie', 'preferredLanguage', 'url', 'baseLocale'],
})

Locale settings are in apps/web/project.inlang/settings.json:

{
  "baseLocale": "en",
  "locales": ["en", "fr"],
  "plugin.inlang.messageFormat": {
    "pathPattern": "./messages/{locale}.json"
  }
}

Adding Translations

1. Add Messages to JSON Files

Add your message to each locale file in apps/web/messages/:

// messages/en.json
{
  "greeting": "Hello, {name}!",
  "items_count": "{count, plural, one {# item} other {# items}}"
}
// messages/fr.json
{
  "greeting": "Bonjour, {name}!",
  "items_count": "{count, plural, one {# article} other {# articles}}"
}

2. Use Messages in Components

Import and call message functions:

import { m } from '@/paraglide/messages'

function Welcome() {
  return (
    <div>
      <h1>{m.greeting({ name: 'World' })}</h1>
      <p>{m.items_count({ count: 5 })}</p>
    </div>
  )
}

Locale Management

Getting the Current Locale

import { getLocale } from '@/paraglide/runtime'

const currentLocale = getLocale() // "en" or "fr"

Changing the Locale

import { setLocale } from '@/paraglide/runtime'

setLocale('fr') // Switch to French

Available Locales

import { locales, baseLocale } from '@/paraglide/runtime'

console.log(locales)    // ["en", "fr"]
console.log(baseLocale) // "en"

Locale Switcher Component

A ready-to-use locale switcher is available at apps/web/src/components/LocaleSwitcher.tsx:

import {
  Button,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@repo/ui'
import { Languages } from 'lucide-react'
import { m } from '@/paraglide/messages'
import { getLocale, locales, setLocale } from '@/paraglide/runtime'

const localeLabels: Record<string, string> = {
  en: 'English',
  fr: 'Français',
}

export function LocaleSwitcher() {
  const currentLocale = getLocale()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" size="icon" aria-label={m.language_label()}>
          <Languages className="h-4 w-4" />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        {locales.map((locale) => (
          <DropdownMenuItem
            key={locale}
            onClick={() => setLocale(locale)}
            className={locale === currentLocale ? 'bg-accent' : ''}
          >
            {localeLabels[locale] ?? locale.toUpperCase()}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Adding a New Locale

  1. Add the locale to apps/web/project.inlang/settings.json:
{
  "locales": ["en", "fr", "es"]
}
  1. Create the translation file apps/web/messages/es.json:
{
  "$schema": "https://inlang.com/schema/inlang-message-format",
  "greeting": "¡Hola, {name}!"
}
  1. Run the dev server to regenerate the paraglide output:
bun dev

Message Syntax

Paraglide uses the ICU Message Format:

Variables

{
  "welcome": "Welcome, {username}!"
}

Pluralization

{
  "notifications": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
}

Select (Gender, etc.)

{
  "invite": "{gender, select, male {He invited you} female {She invited you} other {They invited you}}"
}

Resources

We use cookies to improve your experience. You can accept all, reject all, or customize your preferences.