Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
Version History
- "Update Solid useIntlayer API usage to direct property access"v8.9.004/05/2026
- "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 EnglishIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
Translate your Vite and Vanilla JS website using Intlayer | Internationalisation (i18n)
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.
Step-by-Step Guide to Set Up Intlayer in a Vite and Vanilla JS Application
Step 1: Install Dependencies
Install the necessary packages using npm:
Copy the code to the clipboard
npm install intlayer vanilla-intlayernpm install vite-intlayer --save-devnpx intlayer initintlayer The core package that provides internationalisation tools for configuration management, translation, content declaration, transpilation, and CLI commands.
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.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:
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.
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 content is rendered so that the global locale singleton is ready.
Copy the code to the clipboard
import { installIntlayer } from "vanilla-intlayer";// Must be called before rendering any i18n content.installIntlayer();// Import and run your app modules.import "./app.js";If you also use md() content declarations (Markdown), install the markdown renderer as well:
Copy the code to the clipboard
import { installIntlayer, installIntlayerMarkdown } from "vanilla-intlayer";installIntlayer();installIntlayerMarkdown();import "./app.js";Step 5: Declare Your Content
Create and manage your content declarations to store translations:
Copy the code to the clipboard
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;Your content declarations can be defined anywhere in your application as soon as they are included in the
contentDirdirectory (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: Use Intlayer in Your JavaScript
vanilla-intlayer mirrors the react-intlayer surface API: useIntlayer(key, locale?) returns the translated content directly. Chain .onChange() on the result to subscribe to locale changes - the explicit equivalent of a React re-render.
Copy the code to the clipboard
import { installIntlayer, useIntlayer } from "vanilla-intlayer";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<HTMLHeadingElement>("h1")!.textContent = String( newContent.title ); document.querySelector<HTMLParagraphElement>(".read-the-docs")!.textContent = String(newContent.readTheDocs);});// Initial renderdocument.querySelector<HTMLHeadingElement>("h1")!.textContent = String( content.title);document.querySelector<HTMLParagraphElement>(".read-the-docs")!.textContent = String(content.readTheDocs);Access leaf values as strings by wrapping them in
String(), which calls the node'stoString()method and returns the translated text.When you need the value for a native HTML attribute (e.g.
alt,aria-label), use.valuedirectly:typescriptCopy codeCopy the code to the clipboard
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.
Copy the code to the clipboard
import { getLocaleName } from "intlayer";import { useLocale } from "vanilla-intlayer";export function setupLocaleSwitcher(container: HTMLElement): () => void { const { locale, availableLocales, setLocale, subscribe } = useLocale(); const select = document.createElement("select"); select.setAttribute("aria-label", "Language"); const render = (currentLocale: string) => { 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 as any)); // Keep the dropdown in sync when locale changes from elsewhere return subscribe((newLocale) => render(newLocale));}(Optional) Step 8: Render Markdown and HTML content
Intlayer supports md() and html() content declarations. In vanilla JS, compiled output is inserted as raw HTML via innerHTML.
Compile and inject the HTML:
Copy the code to the clipboard
import { compileMarkdown, installIntlayerMarkdown, useIntlayer,} from "vanilla-intlayer";installIntlayerMarkdown();const content = useIntlayer("app").onChange((newContent) => { const el = document.querySelector<HTMLDivElement>(".edit-note")!; el.innerHTML = compileMarkdown(String(newContent.editNote));});document.querySelector<HTMLDivElement>(".edit-note")!.innerHTML = compileMarkdown(String(content.editNote));TIP String(content.editNote)callstoString()on theIntlayerNodewhich returns the raw Markdown string. Pass it tocompileMarkdownto get an HTML string, then set it viainnerHTML.
WARNING Only use
innerHTMLwith trusted content. If the markdown comes from user input, sanitise it first (e.g. with DOMPurify). You can install a sanitising renderer dynamically:typescriptCopy codeCopy the code to the clipboard
import { installIntlayerMarkdownDynamic } from "vanilla-intlayer";await installIntlayerMarkdownDynamic(async () => { const DOMPurify = await import("dompurify"); return (markdown) => DOMPurify.sanitise(compileMarkdown(markdown));});
(Optional) Step 9: Add localised Routing to your application
To make unique routes for each language (useful for SEO), you can use intlayerProxy in your Vite config for server-side locale detection.
First, add intlayerProxy to your Vite config:
Note that to useintlayerProxyin production, you need to movevite-intlayerfromdevDependenciestodependencies.
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, call useRewriteURL() after installing Intlayer:
Copy the code to the clipboard
import { installIntlayer, useRewriteURL } from "vanilla-intlayer";installIntlayer();// Rewrites the URL immediately and on every subsequent locale change.// Returns an unsubscribe function for cleanup.const stopRewriteURL = useRewriteURL();(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.
Copy the code to the clipboard
import { getHTMLTextDir } from "intlayer";import { installIntlayer, useLocale } from "vanilla-intlayer";installIntlayer();useLocale({ onLocaleChange: (locale) => { document.documentElement.lang = locale; document.documentElement.dir = getHTMLTextDir(locale); },});(Optional) Step 12: Lazy-load dictionaries per locale
For large apps you may want to split each locale's dictionary into its own chunk. Use useDictionaryDynamic alongside Vite's dynamic import():
Copy the code to the clipboard
import { installIntlayer, useDictionaryDynamic } from "vanilla-intlayer";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);});Each locale's bundle is fetched only when that locale becomes active and the result is cached - subsequent switches to the same locale are instant.
(Optional) Step 13: 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:
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;Run the extractor to transform your components and extract the content
Copy the code to the clipboard
npx intlayer extract(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 thexhtml:linknamespace (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
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).
Prefergenerate-seo.mjsfor Node ESM. If you usegenerate-seo.jsinstead, ensure"type": "module"is set inpackage.json, or run Node with ESM enabled.
2. Run the script before Vite
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.
Copy the code to the clipboard
{ "compilerOptions": { // ... }, "include": ["src", ".intlayer/**/*.ts"],}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:
Copy the code to the clipboard
# Ignore the files generated by Intlayer.intlayerVS 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.