HomeSandboxShowcaseAppDocBlog
    • EnglishEnglish
      EN
    • русскийRussian
      RU
    • 日本語Japanese
      JA
    • françaisFrench
      FR
    • 한국어Korean
      KO
    • 中文Chinese
      ZH
    • españolSpanish
      ES
    • DeutschGerman
      DE
    • العربيةArabic
      AR
    • italianoItalian
      IT
    • British EnglishBritish English
      EN-GB
    • portuguêsPortuguese
      PT
    • हिन्दीHindi
      HI
    • TürkçeTurkish
      TR
    • polskiPolish
      PL
    • IndonesiaIndonesian
      ID
    • Tiếng ViệtVietnamese
      VI
    • українськаUkrainian
      UK
    /
    Filter docs by framework
    Alt+←
    Why Intlayer ?
    Get Started
    Concept
    • How Intlayer Works
    • Configuration
    • TestFillBuildWatchExtractLoginPushPullConfigurationListVersionEditorLiveDebugDoc ReviewDoc TranslateSDK
    • Visual Editor
    • CMS
    • CI/CD Integration
    • TranslationPluralEnumerationConditionGenderInsertionFileNestingMarkdownHTMLFunction Fetching
    • Per Locale File
    • Compiler
    • Auto Fill
    • Testing
    • Bundle Optimization
    Environment
    • Next.js 14 and App Router
      Next.js 15
      Next.js no locale path
      Next.js and Page Router
      Compiler
    • Tanstack Start Solid
    • Astro and React
      Astro and Svelte
      Astro and Vue
      Astro and Solid
      Astro and Preact
      Astro and Lit
      Astro and Vanilla JS
    • React Router v7
      React Router v7 (fs-routes)
      Compiler
    • Nuxt and Vue
    • Vite and Solid
    • SvelteKit
    • Vite and Preact
    • Vite and Vanilla JS
    • Vite and Lit
    • Angular 19 (Webpack)
      Analog
    • React CRA
    • React Native and Expo
    • Express.js
      NestJS
      Fastify
      Hono
      Adonis
    • Lynx and React
    Plugins
    • JSON
    • gettext (.po)
    VS Code Extension
    Agent
    • MCP Server
    • Agent skills
    Releases
    • v8
    • v7
    • v6
    Benchmark
    • Next.js
    • TanStack
    • Vue
    • Solid
    • Svelte
    Blog
    Ask a question
    1. Documentation
    2. Environment
    3. Vite and Lit
    Creation:2026-03-23Last update:2026-05-06
    See the application template on GitHub

    This page has an application template available.

    See the showcase application

    This page links to a live demo of the template.

    Reference this doc to your favorite AI assistant
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Ask your question and get a summary of the document by referencing this page and the AI provider of your choice

    Version History

    1. "Update Solid useIntlayer API usage to direct property access"
      v8.9.004/05/2026
    2. "Initial history"
      v8.4.1023/03/2026

    The content of this page was translated using an AI.

    See the last version of the original content in English
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    Translate your Vite and Lit website using Intlayer | Internationalisation (i18n)

    ide.intlayer.org
    intlayer-vite-lit-template.vercel.app

    Table of Contents

    What is Intlayer?

    Intlayer is an innovative, open-source internationalisation (i18n) library designed to simplify multilingual support in modern web applications.

    With Intlayer, you can:

    • Easily manage translations using declarative dictionaries at the component level.
    • Dynamically localise metadata, routes, and content.
    • Ensure TypeScript support with autogenerated types, improving autocompletion and error detection.
    • Benefit from advanced features, like dynamic locale detection and switching.

    See Application Template on GitHub.

    Step-by-Step Guide to Set Up Intlayer in a Vite and Lit Application

    Step 1: Install Dependencies

    Install the necessary packages using npm:

    bash
    Copy code

    Copy the code to the clipboard

    npm install intlayer lit-intlayernpm install vite-intlayer --save-devnpx intlayer init
    • intlayer

      The core package that provides internationalisation tools for configuration management, translation, content declaration, transpilation, and CLI commands.

    • lit-intlayer The package that integrates Intlayer with Lit applications. It provides ReactiveController-based hooks (useIntlayer, useLocale, etc.) so that LitElements automatically re-render when the locale changes.

    • vite-intlayer Includes the Vite plugin for integrating Intlayer with the Vite bundler, as well as middleware for detecting the user's preferred locale, managing cookies, and handling URL redirection.

    Step 2: Configuration of your project

    Create a config file to configure the languages of your application:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [
          Locales.ENGLISH,
          Locales.FRENCH,
          Locales.SPANISH,
          // Your other locales
        ],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    Through this configuration file, you can set up localised URLs, middleware redirection, cookie names, the location and extension of your content declarations, disable Intlayer logs in the console, and more. For a complete list of available parameters, refer to the configuration documentation.

    Step 3: Integrate Intlayer in Your Vite Configuration

    Add the intlayer plugin into your configuration.

    vite.config.ts
    Copy code

    Copy the code to the clipboard

    import { defineConfig } from "vite";
    import { intlayer } from "vite-intlayer";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [intlayer()],
    });
    The intlayer() Vite plugin is used to integrate Intlayer with Vite. It ensures the building of content declaration files and monitors them in development mode. It defines Intlayer environment variables within the Vite application. Additionally, it provides aliases to optimise performance.

    Step 4: Bootstrap Intlayer in your entry point

    Call installIntlayer() before any custom elements are registered so that the global locale singleton is ready when the first element connects.

    src/main.ts
    Copy code

    Copy the code to the clipboard

    import { installIntlayer } from "lit-intlayer";// Must be called before any LitElement is connected to the DOM.installIntlayer();// Import and register your custom elements.import "./my-element.js";

    If you also use md() content declarations (Markdown), install the markdown renderer as well:

    src/main.ts
    Copy code

    Copy the code to the clipboard

    import { installIntlayer, installIntlayerMarkdown } from "lit-intlayer";installIntlayer();installIntlayerMarkdown();import "./my-element.js";

    Step 5: Declare Your Content

    Create and manage your content declarations to store translations:

    src/app.content.ts
    Copy code

    Copy the code to the clipboard

    import { t, type Dictionary } from "intlayer";
    
    const appContent = {
      key: "app",
      content: {
        title: "Vite + Lit",
    
        viteLogo: t({
          en: "Vite logo",
          fr: "Logo Vite",
          es: "Logo Vite",
        }),
        litLogo: t({
          en: "Lit logo",
          fr: "Logo Lit",
          es: "Logo Lit",
        }),
    
        count: t({
          en: "count is {{count}}",
          fr: "le compte est {{count}}",
          es: "el recuento es {{count}}",
        }),
    
        readTheDocs: t({
          en: "Click on the Vite and Lit logos to learn more",
          fr: "Cliquez sur les logos Vite et Lit pour en savoir plus",
          es: "Haga clic en los logotipos de Vite y Lit para obtener más información",
        }),
      },
    } satisfies Dictionary;
    
    export default appContent;

    Your content declarations can be defined anywhere in your application as soon as they are included in the contentDir directory (by default, ./src). And match the content declaration file extension (by default, .content.{json,ts,tsx,js,jsx,mjs,cjs}).

    For more details, refer to the content declaration documentation.

    Step 6: Utilise Intlayer in Your LitElement

    Use useIntlayer inside a LitElement. It returns a ReactiveController proxy that automatically triggers re-renders whenever the active locale changes - no extra setup required.

    src/my-element.ts
    Copy code

    Copy the code to the clipboard

    import { LitElement, html } from "lit";import { customElement, property } from "lit/decorators.js";import { useIntlayer } from "lit-intlayer";@customElement("my-element")export class MyElement extends LitElement {  @property({ type: Number })  count = 0;  // useIntlayer registers itself as a ReactiveController.  // The element re-renders automatically when the locale changes.  private content = useIntlayer(this, "app");  override render() {    const { content } = this;    return html`      <h1>${content.title}</h1>      <img src="/vite.svg" alt=${content.viteLogo.value} />      <img src="/lit.svg" alt=${content.litLogo.value} />      <button @click=${() => this.count++}>        ${content.count({ count: this.count })}      </button>      <p>${content.readTheDocs}</p>    `;  }}

    When you need the translated string in a native HTML attribute (e.g. alt, aria-label, title), you can use the translated string in several ways:

    typescript
    Copy code

    Copy the code to the clipboard

    html`<img alt=${content.viteLogo.value} />`;html`<img alt=${content.viteLogo.toString()} />`;html`<img alt=${String(content.viteLogo)} />`;

    (Optional) Step 7: Change the language of your content

    To change the language of your content, use the setLocale method exposed by the useLocale controller.

    src/locale-switcher.ts
    Copy code

    Copy the code to the clipboard

    import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getLocaleName } from "intlayer";import { useLocale } from "lit-intlayer";@customElement("locale-switcher")export class LocaleSwitcher extends LitElement {  private locale = useLocale(this);  private _onChange(e: Event) {    const select = e.target as HTMLSelectElement;    this.locale.setLocale(select.value as any);  }  override render() {    return html`      <select @change=${this._onChange}>        ${this.locale.availableLocales.map(          (loc) => html`            <option value=${loc} ?selected=${loc === this.locale.locale}>              ${getLocaleName(loc)}            </option>          `        )}      </select>    `;  }}

    (Optional) Step 8: Render Markdown and HTML content

    Intlayer supports md() and html() content declarations. In Lit, compiled output is injected as raw HTML via the unsafeHTML directive.

    Render the compiled HTML in your element:

    src/my-element.ts
    Copy code

    Copy the code to the clipboard

    import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { unsafeHTML } from "lit/directives/unsafe-html.js";import { useIntlayer } from "lit-intlayer";import { compileMarkdown } from "lit-intlayer/markdown";@customElement("my-element")export class MyElement extends LitElement {  private content = useIntlayer(this, "app");  override render() {    return html`      <div class="edit-note">        ${unsafeHTML(compileMarkdown(String(this.content.editNote)))}      </div>    `;  }}
    TIP
    String(content.editNote) calls toString() on the IntlayerNode, which returns the raw Markdown string. Pass it to compileMarkdown to get an HTML string, then render it with Lit's unsafeHTML directive.

    (Optional) Step 9: Add localised Routing to your application

    To make unique routes for each language (useful for SEO), you can use a client-side router alongside Intlayer's localeMap / localeFlatMap helpers, and the intlayerProxy Vite plugin for server-side locale detection.

    First, add intlayerProxy to your Vite config:

    Note that to use intlayerProxy in production, you need to move vite-intlayer from devDependencies to dependencies.
    vite.config.ts
    Copy code

    Copy the code to the clipboard

    import { defineConfig } from "vite";
    import { intlayer, intlayerProxy } from "vite-intlayer";
    
    export default defineConfig({
      plugins: [
        intlayerProxy(), // should be placed first
        intlayer(),
      ],
    });

    (Optional) Step 10: Change the URL when the locale changes

    To update the browser URL when the locale changes, use useRewriteURL alongside the locale switcher:

    src/locale-switcher.ts
    Copy code

    Copy the code to the clipboard

    import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getLocaleName, getLocalizedUrl } from "intlayer";import { useLocale, useRewriteURL } from "lit-intlayer";@customElement("locale-switcher")export class LocaleSwitcher extends LitElement {  private locale = useLocale(this);  // Automatically rewrites the current URL when the locale changes.  private _rewriteURL = useRewriteURL(this);  private _onChange(e: Event) {    const select = e.target as HTMLSelectElement;    this.locale.setLocale(select.value as any);  }  override render() {    return html`      <select @change=${this._onChange}>        ${this.locale.availableLocales.map(          (loc) => html`            <option value=${loc} ?selected=${loc === this.locale.locale}>              ${getLocaleName(loc)}            </option>          `        )}      </select>    `;  }}

    (Optional) Step 11: Switch the HTML Language and Direction Attributes

    Update the <html> tag's lang and dir attributes to match the current locale for accessibility and SEO.

    src/my-element.ts
    Copy code

    Copy the code to the clipboard

    import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getHTMLTextDir } from "intlayer";import { useLocale } from "lit-intlayer";@customElement("my-element")export class MyElement extends LitElement {  private locale = useLocale(this, {    onLocaleChange: (loc) => {      document.documentElement.lang = loc;      document.documentElement.dir = getHTMLTextDir(loc);    },  });  override render() {    return html`<!-- your content -->`;  }}

    (Optional) Step 12: Extract the content of your components

    If you have an existing codebase, transforming thousands of files can be time-consuming.

    To ease this process, Intlayer proposes a compiler / extractor to transform your components and extract the content.

    To set it up, you can add a compiler section in your intlayer.config.ts file:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { type IntlayerConfig } from "intlayer";const config: IntlayerConfig = {  // ... Rest of your config  compiler: {    /**     * Indicates if the compiler should be enabled.     */    enabled: true,    /**     * Defines the output files path     */    output: ({ fileName, extension }) => `./${fileName}${extension}`,    /**     * Indicates if the components should be saved after being transformed.     *     * - If `true`, the compiler will rewrite the component file in the disk. So the transformation will be permanent, and the compiler will skip the transformation for the next process. That way, the compiler can transform the app, and then it can be removed.     *     * - If `false`, the compiler will inject the `useIntlayer()` function call into the code in the build output only, and keep the base codebase intact. The transformation will be done only in memory.     */    saveComponents: false,    /**     * Dictionary key prefix     */    dictionaryKeyPrefix: "",  },};export default config;

    (Optional) Sitemap and robots.txt (build-time)

    Intlayer includes formatters such as generateSitemap and getMultilingualUrls that produce crawler-ready multilingual sitemap.xml and robots.txt output you can write into your project’s public/ folder. In practice you run a small Node script before Vite (for example predev / prebuild npm hooks) so those files exist when you build or serve the app.

    Sitemap

    Intlayer’s sitemap generator respects your locale setup and includes the usual metadata for crawlers.

    The generated sitemap supports the xhtml:link namespace (hreflang XML extensions). Unlike basic generators that only emit flat URLs, Intlayer wires bidirectional links between every localized variant of each page (for example /about, /fr/about, or /about?lang=fr, depending on your routing mode), which helps search engines relate localized URLs.

    Robots.txt

    Use getMultilingualUrls so Disallow entries cover every localized spelling of sensitive paths.

    1. Add generate-seo.mjs at the project root

    generate-seo.mjs
    Copy code

    Copy the code to the clipboard

    import fs from "fs";import path from "path";import { fileURLToPath } from "url";import { generateSitemap, getMultilingualUrls } from "intlayer";const __dirname = path.dirname(fileURLToPath(import.meta.url));const SITE_URL = (process.env.SITE_URL || "http://localhost:5173").replace(  /\/$/,  "");const pathList = [  { path: "/", changefreq: "daily", priority: 1.0 },  { path: "/about", changefreq: "monthly", priority: 0.7 },];const sitemapXml = generateSitemap(pathList, { siteUrl: SITE_URL });fs.writeFileSync(path.join(__dirname, "public", "sitemap.xml"), sitemapXml);const getAllMultilingualUrls = (urls) =>  urls.flatMap((url) => Object.values(getMultilingualUrls(url)));const disallowedPaths = getAllMultilingualUrls(["/admin", "/private"]);const robotsTxt = [  "User-agent: *",  "Allow: /",  ...disallowedPaths.map((path) => `Disallow: ${path}`),  "",  `Sitemap: ${SITE_URL}/sitemap.xml`,].join("\n");fs.writeFileSync(path.join(__dirname, "public", "robots.txt"), robotsTxt);console.log("SEO files generated successfully.");

    intlayer must be installed so the script can import it. Set SITE_URL in the environment for production (for example in CI).

    Prefer generate-seo.mjs for Node ESM. If you use generate-seo.js instead, ensure "type": "module" is set in package.json, or run Node with ESM enabled.

    2. Run the script before Vite

    package.json
    Copy code

    Copy the code to the clipboard

    {  "scripts": {    "dev": "vite",    "prebuild": "node generate-seo.mjs",    "build": "vite build",    "preview": "vite preview"  }}

    Adjust if you use pnpm or yarn. You can also invoke the same script from CI or another step if that fits your workflow.

    Configure TypeScript

    Ensure your TypeScript configuration includes the autogenerated types.

    tsconfig.json
    Copy code

    Copy the code to the clipboard

    {  "compilerOptions": {    // ...    "experimentalDecorators": true,    "useDefineForClassFields": false,  },  "include": ["src", ".intlayer/**/*.ts"],}
    experimentalDecorators and useDefineForClassFields: false are required by Lit for decorator support.

    Git Configuration

    It is recommended to ignore the files generated by Intlayer. This allows you to avoid committing them to your Git repository.

    To do this, you can add the following instructions to your .gitignore file:

    bash
    Copy code

    Copy the code to the clipboard

    # Ignore the files generated by Intlayer.intlayer

    VS Code Extension

    To improve your development experience with Intlayer, you can install the official Intlayer VS Code Extension.

    Install from the VS Code Marketplace

    This extension provides:

    • Autocompletion for translation keys.
    • Real-time error detection for missing translations.
    • Inline previews of translated content.
    • Quick actions to easily create and update translations.

    For more details on how to use the extension, refer to the Intlayer VS Code Extension documentation.


    Go Further

    To go further, you can implement the visual editor or externalise your content using the CMS.

    Vite and Vanilla JS
    Angular 21
    Alt+→

    On this page

      Discussions are anonymous and regularly reviewed to address common issues. Feel free to share feature ideas, feedback on the documentation, or anything related to Intlayer, we use this input to shape our roadmap and improve the product.

      npm install intlayer lit-intlayernpm install vite-intlayer --save-devnpx intlayer init
      import { installIntlayer } from "lit-intlayer";// Must be called before any LitElement is connected to the DOM.installIntlayer();// Import and register your custom elements.import "./my-element.js";
      import { installIntlayer, installIntlayerMarkdown } from "lit-intlayer";installIntlayer();installIntlayerMarkdown();import "./my-element.js";
      import { LitElement, html } from "lit";import { customElement, property } from "lit/decorators.js";import { useIntlayer } from "lit-intlayer";@customElement("my-element")export class MyElement extends LitElement {  @property({ type: Number })  count = 0;  // useIntlayer registers itself as a ReactiveController.  // The element re-renders automatically when the locale changes.  private content = useIntlayer(this, "app");  override render() {    const { content } = this;    return html`      <h1>${content.title}</h1>      <img src="/vite.svg" alt=${content.viteLogo.value} />      <img src="/lit.svg" alt=${content.litLogo.value} />      <button @click=${() => this.count++}>        ${content.count({ count: this.count })}      </button>      <p>${content.readTheDocs}</p>    `;  }}
      html`<img alt=${content.viteLogo.value} />`;html`<img alt=${content.viteLogo.toString()} />`;html`<img alt=${String(content.viteLogo)} />`;
      import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getLocaleName } from "intlayer";import { useLocale } from "lit-intlayer";@customElement("locale-switcher")export class LocaleSwitcher extends LitElement {  private locale = useLocale(this);  private _onChange(e: Event) {    const select = e.target as HTMLSelectElement;    this.locale.setLocale(select.value as any);  }  override render() {    return html`      <select @change=${this._onChange}>        ${this.locale.availableLocales.map(          (loc) => html`            <option value=${loc} ?selected=${loc === this.locale.locale}>              ${getLocaleName(loc)}            </option>          `        )}      </select>    `;  }}
      import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { unsafeHTML } from "lit/directives/unsafe-html.js";import { useIntlayer } from "lit-intlayer";import { compileMarkdown } from "lit-intlayer/markdown";@customElement("my-element")export class MyElement extends LitElement {  private content = useIntlayer(this, "app");  override render() {    return html`      <div class="edit-note">        ${unsafeHTML(compileMarkdown(String(this.content.editNote)))}      </div>    `;  }}
      import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getLocaleName, getLocalizedUrl } from "intlayer";import { useLocale, useRewriteURL } from "lit-intlayer";@customElement("locale-switcher")export class LocaleSwitcher extends LitElement {  private locale = useLocale(this);  // Automatically rewrites the current URL when the locale changes.  private _rewriteURL = useRewriteURL(this);  private _onChange(e: Event) {    const select = e.target as HTMLSelectElement;    this.locale.setLocale(select.value as any);  }  override render() {    return html`      <select @change=${this._onChange}>        ${this.locale.availableLocales.map(          (loc) => html`            <option value=${loc} ?selected=${loc === this.locale.locale}>              ${getLocaleName(loc)}            </option>          `        )}      </select>    `;  }}
      import { LitElement, html } from "lit";import { customElement } from "lit/decorators.js";import { getHTMLTextDir } from "intlayer";import { useLocale } from "lit-intlayer";@customElement("my-element")export class MyElement extends LitElement {  private locale = useLocale(this, {    onLocaleChange: (loc) => {      document.documentElement.lang = loc;      document.documentElement.dir = getHTMLTextDir(loc);    },  });  override render() {    return html`<!-- your content -->`;  }}
      import { type IntlayerConfig } from "intlayer";const config: IntlayerConfig = {  // ... Rest of your config  compiler: {    /**     * Indicates if the compiler should be enabled.     */    enabled: true,    /**     * Defines the output files path     */    output: ({ fileName, extension }) => `./${fileName}${extension}`,    /**     * Indicates if the components should be saved after being transformed.     *     * - If `true`, the compiler will rewrite the component file in the disk. So the transformation will be permanent, and the compiler will skip the transformation for the next process. That way, the compiler can transform the app, and then it can be removed.     *     * - If `false`, the compiler will inject the `useIntlayer()` function call into the code in the build output only, and keep the base codebase intact. The transformation will be done only in memory.     */    saveComponents: false,    /**     * Dictionary key prefix     */    dictionaryKeyPrefix: "",  },};export default config;
      import fs from "fs";import path from "path";import { fileURLToPath } from "url";import { generateSitemap, getMultilingualUrls } from "intlayer";const __dirname = path.dirname(fileURLToPath(import.meta.url));const SITE_URL = (process.env.SITE_URL || "http://localhost:5173").replace(  /\/$/,  "");const pathList = [  { path: "/", changefreq: "daily", priority: 1.0 },  { path: "/about", changefreq: "monthly", priority: 0.7 },];const sitemapXml = generateSitemap(pathList, { siteUrl: SITE_URL });fs.writeFileSync(path.join(__dirname, "public", "sitemap.xml"), sitemapXml);const getAllMultilingualUrls = (urls) =>  urls.flatMap((url) => Object.values(getMultilingualUrls(url)));const disallowedPaths = getAllMultilingualUrls(["/admin", "/private"]);const robotsTxt = [  "User-agent: *",  "Allow: /",  ...disallowedPaths.map((path) => `Disallow: ${path}`),  "",  `Sitemap: ${SITE_URL}/sitemap.xml`,].join("\n");fs.writeFileSync(path.join(__dirname, "public", "robots.txt"), robotsTxt);console.log("SEO files generated successfully.");
      {  "scripts": {    "dev": "vite",    "prebuild": "node generate-seo.mjs",    "build": "vite build",    "preview": "vite preview"  }}
      {  "compilerOptions": {    // ...    "experimentalDecorators": true,    "useDefineForClassFields": false,  },  "include": ["src", ".intlayer/**/*.ts"],}
      # Ignore the files generated by Intlayer.intlayer