---
createdAt: 2026-03-31
updatedAt: 2026-05-06
title: Vanilla JS i18n - How to translate a Vanilla JS app in 2026
description: Discover how to make your Vanilla JS website multilingual. Follow the documentation to internationalize (i18n) and translate it.
keywords:
  - Internationalization
  - Documentation
  - Intlayer
  - Vanilla JS
  - JavaScript
  - TypeScript
  - HTML
slugs:
  - doc
  - environment
  - vanilla
applicationTemplate: https://github.com/aymericzip/intlayer-vanilla-template
applicationShowcase: https://intlayer-vanilla-template.vercel.app
history:
  - version: 8.9.0
    date: 2026-05-04
    changes: "Update Solid useIntlayer API usage to direct property access"
  - version: 8.4.10
    date: 2026-03-31
    changes: "Initial history"
---

# Translate your Vanilla JS website using Intlayer | Internationalization (i18n)

<Tabs defaultTab="code">
  <Tab label="Code" value="code">

<iframe
  src="https://ide.intlayer.org/aymericzip/intlayer-vanilla-template?file=intlayer.config.ts"
  className="m-auto overflow-hidden rounded-lg border-0 max-md:size-full max-md:h-[700px] md:aspect-16/9 md:w-full"
  title="Demo CodeSandbox - Intlayer"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
  loading="lazy"
/>

  </Tab>
  <Tab label="Demo" value="demo">

<iframe
  src="https://intlayer-vanilla-template.vercel.app"
  className="m-auto overflow-hidden rounded-lg border-0 max-md:size-full max-md:h-[700px] md:aspect-16/9 md:w-full"
  title="Demo - intlayer-vanilla-template"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
  loading="lazy"
/>

  </Tab>
</Tabs>

## Table of Contents

<TOC/>

## What is Intlayer?

**Intlayer** is an innovative, open-source internationalization (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 localize 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.

This guide demonstrates how to use Intlayer in a Vanilla JavaScript application **without using a package manager or a bundler** (like Vite, Webpack, etc.).

If your application uses a bundler (like Vite), we recommend following the [Vite + Vanilla JS Guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_with_vite+vanilla.md) instead.

Using the standalone bundle, you can import Intlayer directly into your HTML files via a single JavaScript file, making it perfect for legacy projects or simple static sites.

---

## Step-by-Step Guide to Set Up Intlayer in a Vanilla JS Application

### Step 1: Install Dependencies

Install the necessary packages using npm:

```bash packageManager="npm"
# Generate a standalone bundle of intlayer and vanilla-intlayer
# This file will be imported in to your HTML file
npx intlayer standalone --packages intlayer vanilla-intlayer --outfile intlayer.js

# Initialize intlayer with config file
npx intlayer init --no-gitignore

# Build the dictionaries
npx intlayer build
```

```bash packageManager="pnpm"
# Generate a standalone bundle of intlayer and vanilla-intlayer
# This file will be imported in to your HTML file
pnpm intlayer standalone --packages intlayer vanilla-intlayer --outfile intlayer.js

# Initialize intlayer with config file
pnpm intlayer init --no-gitignore

# Build the dictionaries
pnpm intlayer build
```

```bash packageManager="yarn"
# Generate a standalone bundle of intlayer and vanilla-intlayer
# This file will be imported in to your HTML file
yarn intlayer standalone --packages intlayer vanilla-intlayer --outfile intlayer.js

# Initialize intlayer config file, TypeScript if set up, env var
yarn intlayer init --no-gitignore

# Build the dictionaries
yarn intlayer build
```

```bash packageManager="bun"
# Generate a standalone bundle of intlayer and vanilla-intlayer
# This file will be imported in to your HTML file
bun x intlayer standalone --packages intlayer vanilla-intlayer --outfile intlayer.js

# Initialize intlayer with config file
bun x intlayer init --no-gitignore

# Build the dictionaries
bun x intlayer build
```

- **intlayer**
  The core package that provides internationalization tools for configuration management, translation, [content declaration](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md), transpilation, and [CLI commands](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/cli/index.md).

- **vanilla-intlayer**
  The package that integrates Intlayer with plain JavaScript / TypeScript applications. It provides a pub/sub singleton (`IntlayerClient`) and callback-based helpers (`useIntlayer`, `useLocale`, etc.) so any part of your app can react to locale changes without depending on a UI framework.

> The `intlayer standalone` CLI's bundling export produces an optimized build by tree-shaking unused packages, locales, and non-essential logic (such as redirection or prefixes) specific to your configuration.

### Step 2: Configuration of your project

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

```typescript fileName="intlayer.config.ts" codeFormat={["typescript", "esm", "commonjs"]}
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 localized 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](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md).

### Step 3: Import the bundle in your HTML

Once you have generated the `intlayer.js` bundle, you can import it in your HTML file:

```html fileName="index.html"
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />

    <!-- Import the bundle -->
    <script src="./intlayer.js" defer></script>
    <!-- Import your main script -->
    <script src="./src/main.js" defer></script>
  </head>
  <body>
    <h1 id="title"></h1>
    <p class="read-the-docs"></p>
  </body>
</html>
```

The bundle exposes `Intlayer` and `VanillaIntlayer` as global objects on the `window`.

### Step 4: Bootstrap Intlayer in your entry point

In your `src/main.js`, call `installIntlayer()` **before** any content is rendered so that the global locale singleton is ready.

```javascript fileName="src/main.js"
const { installIntlayer } = window.VanillaIntlayer;

// Must be called before rendering any i18n content.
installIntlayer();
```

If you also want to use the markdown renderer, call `installIntlayerMarkdown()`:

```javascript fileName="src/main.js"
const { installIntlayer, installIntlayerMarkdown } = window.VanillaIntlayer;

installIntlayer();
installIntlayerMarkdown();
```

### Step 5: Declare Your Content

Create and manage your content declarations to store translations:

```typescript fileName="src/app.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
import { insert, t, type Dictionary } from "intlayer";

const appContent = {
  key: "app",
  content: {
    title: "Vite + Vanilla",

    viteLogoLabel: t({
      en: "Vite Logo",
      fr: "Logo Vite",
      es: "Logo Vite",
    }),

    count: insert(
      t({
        en: "count is {{count}}",
        fr: "le compte est {{count}}",
        es: "el recuento es {{count}}",
      })
    ),

    readTheDocs: t({
      en: "Click on the Vite logo to learn more",
      fr: "Cliquez sur le logo Vite pour en savoir plus",
      es: "Haga clic en el logotipo de Vite para obtener más información",
    }),
  },
} satisfies Dictionary;

export default appContent;
```

```json fileName="src/app.content.json" contentDeclarationFormat="json"
{
  "$schema": "https://intlayer.org/schema.json",
  "key": "app",
  "content": {
    "title": "Vite + Vanilla",
    "viteLogoLabel": {
      "nodeType": "translation",
      "translation": {
        "en": "Vite Logo",
        "fr": "Logo Vite",
        "es": "Logo Vite"
      }
    },
    "count": {
      "nodeType": "insertion",
      "insertion": {
        "nodeType": "translation",
        "translation": {
          "en": "count is {{count}}",
          "fr": "le compte est {{count}}",
          "es": "el recuento es {{count}}"
        }
      }
    },
    "readTheDocs": {
      "nodeType": "translation",
      "translation": {
        "en": "Click on the Vite logo to learn more",
        "fr": "Cliquez sur le logo Vite pour en savoir plus",
        "es": "Haga clic en el logotipo de Vite para obtener más información"
      }
    }
  }
}
```

> 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](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md).

### Step 6: Use Intlayer in Your JavaScript

The `window.VanillaIntlayer` object provides API helpers: `useIntlayer(key, locale?)` returns the translated content for a given key.

```javascript fileName="src/main.js"
const { installIntlayer, useIntlayer } = window.VanillaIntlayer;

installIntlayer();

// Get the initial content for the current locale.
// Chain .onChange() to be notified whenever the locale changes.
const content = useIntlayer("app").onChange((newContent) => {
  // Re-render or patch only the affected DOM nodes
  document.querySelector("h1").textContent = String(newContent.title);
  document.querySelector(".read-the-docs").textContent = String(
    newContent.readTheDocs
  );
});

// Initial render
document.querySelector("h1").textContent = String(content.title);
document.querySelector(".read-the-docs").textContent = String(
  content.readTheDocs
);
```

> Access leaf values as strings by wrapping them in `String()`, which calls the node's `toString()` method and returns the translated text.
>
> When you need the value for a native HTML attribute (e.g. `alt`, `aria-label`), use `.value` directly:
>
> ```javascript
> img.alt = content.viteLogoLabel.value;
> ```

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

To change the language of your content, use the `setLocale` function exposed by `useLocale`.

```javascript fileName="src/locale-switcher.js"
const { getLocaleName } = window.Intlayer;
const { useLocale } = window.VanillaIntlayer;

export function setupLocaleSwitcher(container) {
  const { locale, availableLocales, setLocale, subscribe } = useLocale();

  const select = document.createElement("select");
  select.setAttribute("aria-label", "Language");

  const render = (currentLocale) => {
    select.innerHTML = availableLocales
      .map(
        (loc) =>
          `<option value="${loc}"${loc === currentLocale ? " selected" : ""}>
            ${getLocaleName(loc)}
          </option>`
      )
      .join("");
  };

  render(locale);
  container.appendChild(select);

  select.addEventListener("change", () => setLocale(select.value));

  // Keep the dropdown in sync when locale changes from elsewhere
  return subscribe((newLocale) => render(newLocale));
}
```

### (Optional) Step 8: 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.

```javascript fileName="src/main.js"
const { getHTMLTextDir } = window.Intlayer;
const { installIntlayer, useLocale } = window.VanillaIntlayer;

installIntlayer();

useLocale({
  onLocaleChange: (locale) => {
    document.documentElement.lang = locale;
    document.documentElement.dir = getHTMLTextDir(locale);
  },
});
```

### (Optional) Step 9: Lazy-load dictionaries per locale

If you want to lazy-load dictionaries per locale, you can use `useDictionaryDynamic`. This is useful if you don't want to bundle all translations in the initial `intlayer.js` file.

```javascript fileName="src/app.js"
const { installIntlayer, useDictionaryDynamic } = window.VanillaIntlayer;

installIntlayer();

const unsubscribe = useDictionaryDynamic(
  {
    en: () => import("../.intlayer/dictionaries/en/app.mjs"),
    fr: () => import("../.intlayer/dictionaries/fr/app.mjs"),
    es: () => import("../.intlayer/dictionaries/es/app.mjs"),
  },
  "app"
).onChange((content) => {
  document.querySelector("h1").textContent = String(content.title);
});
```

> Note: `useDictionaryDynamic` requires the dictionaries to be available as separate ESM files. This approach is typically used if you have a web server serving the dictionaries.

### Configure TypeScript

Ensure your TypeScript configuration includes the autogenerated types.

```json5 fileName="tsconfig.json"
{
  "compilerOptions": {
    // ...
  },
  "include": ["src", ".intlayer/**/*.ts"],
}
```

### 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](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)

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](https://intlayer.org/doc/vs-code-extension).

---

### Go Further

To go further, you can implement the [visual editor](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md) or externalize your content using the [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md).
