Vite + React Integration

@lingo.dev/compiler integrates with Vite through a plugin that works with both SPA and SSR setups.

Setup

1. Install Package

pnpm install @lingo.dev/compiler

2. Configure Vite

Add lingoCompilerPlugin to your Vite config:

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { lingoCompilerPlugin } from "@lingo.dev/compiler/vite";

export default defineConfig({
  plugins: [
    lingoCompilerPlugin({
      sourceRoot: "src",
      sourceLocale: "en",
      targetLocales: ["es", "de", "fr"],
      models: "lingo.dev",
      dev: {
        usePseudotranslator: true,
      },
    }),
    react(),
  ],
});

Plugin order: Place lingoCompilerPlugin before react() plugin. This ensures the compiler transforms your JSX before React processes it.

3. Add Provider

Wrap your app with LingoProvider in your entry point:

// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { LingoProvider } from "@lingo.dev/compiler/react";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <LingoProvider>
      <App />
    </LingoProvider>
  </StrictMode>
);

Important: Place LingoProvider as high as possible in your component tree. If using TanStack Router or React Router, place LingoProvider above the router provider.

SPA Setup

For single-page applications, the setup above is sufficient. Locale is managed client-side.

Language Switcher

"use client";

import { useLingoContext } from "@lingo.dev/compiler/react";

export function LanguageSwitcher() {
  const { locale, setLocale } = useLingoContext();

  return (
    <div>
      <label>Language:</label>
      <select value={locale} onChange={(e) => setLocale(e.target.value)}>
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="de">Deutsch</option>
        <option value="fr">Français</option>
      </select>
    </div>
  );
}

SSR Setup (React Router, Remix, TanStack Start)

For SSR frameworks, you may need to handle locale detection on the server.

Custom Locale Detection

Create .lingo/locale-resolver.server.ts for server-side logic:

// .lingo/locale-resolver.server.ts
export async function getServerLocale(): Promise<string> {
  // Access request context (framework-specific)
  // Example: parse cookies, headers, or database
  return "en"; // Return detected locale
}

And .lingo/locale-resolver.client.ts for client-side:

// .lingo/locale-resolver.client.ts
export function getClientLocale(): string {
  return localStorage.getItem("locale") || "en";
}

export function persistLocale(locale: string): void {
  localStorage.setItem("locale", locale);
}

See Custom Locale Resolvers for framework-specific examples.

TanStack Router Integration

For TanStack Router, place LingoProvider above RouterProvider:

// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { LingoProvider } from "@lingo.dev/compiler/react";
import { routeTree } from "./routeTree.gen";

const router = createRouter({ routeTree });

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <LingoProvider>
      <RouterProvider router={router} />
    </LingoProvider>
  </StrictMode>
);

This ensures translations are available throughout your routed components and context isn't broken by code splitting.

React Router Integration

For React Router v6/v7:

// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { LingoProvider } from "@lingo.dev/compiler/react";
import App from "./App";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <LingoProvider>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </LingoProvider>
  </StrictMode>
);

HMR and Development

The compiler fully supports Vite's Hot Module Replacement (HMR). When you update translatable text:

  1. The compiler detects the change
  2. Translation server generates new translations (or pseudotranslations)
  3. HMR updates your component without full reload
  4. Component state is preserved

Fast Refresh works normally—the compiler doesn't interfere with Vite's HMR.

Build Configuration

Development Build

{
  dev: {
    usePseudotranslator: true, // Fast fake translations
  }
}

Run npm run dev for instant feedback with pseudotranslations.

Production Build

{
  buildMode: "cache-only", // Use pre-generated translations
}

Run npm run build. Translations come from .lingo/metadata.json—no API calls needed.

Best practice: Generate real translations in CI before production builds. See Build Modes.

Code Splitting

The compiler respects Vite's code splitting. Each lazy-loaded chunk includes only the translations it needs.

// Lazy-loaded route
const Dashboard = lazy(() => import("./pages/Dashboard"));

// Dashboard component's translations are bundled with the Dashboard chunk

Translations are automatically tree-shaken—only used translations are included in each chunk.

TypeScript

The compiler is fully typed:

import type { LingoConfig } from "@lingo.dev/compiler";

const config: LingoConfig = {
  sourceRoot: "src",
  sourceLocale: "en",
  targetLocales: ["es", "de"],
  models: "lingo.dev",
};

Environment Variables

Use Vite's environment variable system for API keys:

# .env
VITE_LINGO_API_KEY=your_key_here

Access in config:

{
  models: "lingo.dev",
  // API key is automatically read from LINGODOTDEV_API_KEY env variable
}

Never commit API keys. Add .env to .gitignore.

Common Issues

"Cannot find module '@lingo.dev/compiler/react'" Ensure the package is installed: pnpm install @lingo.dev/compiler

HMR breaks after adding LingoProvider Check that lingoCompilerPlugin is placed before react() plugin in your Vite config.

Translations not showing in production Verify buildMode: "cache-only" and that .lingo/metadata.json has translations for all locales.

Context broken with code splitting Ensure LingoProvider is placed above your router provider (TanStack Router, React Router, etc.). Otherwise, code-split routes may lose context.

Port 60000 already in use The translation server auto-finds available ports (60000-60099). If all are in use, configure manually:

{
  dev: {
    translationServerStartPort: 61000, // Use different port range
  }
}

Next Steps