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. Releases
    3. v8
    \n\n \n ```\n\n \n \n\n **1. Using the Component:**\n\n ```svelte\n \n\n \n ```\n\n **2. Using the Hook:**\n\n ```svelte\n \n\n {@html render(\"# My Title\")}\n ```\n\n **3. Using the Utility Function:**\n\n ```svelte\n \n\n {@html renderMarkdown(\"# My Title\")}\n ```\n\n \n \n\n **1. Using the Service:**\n\n ```typescript\n import { Component } from \"@angular/core\";\n import { IntlayerMarkdownService } from \"angular-intlayer\";\n\n @Component({ ... })\n export class MyComponent {\n constructor(private markdownService: IntlayerMarkdownService) {}\n\n render(markdown: string) {\n return this.markdownService.renderMarkdown(markdown);\n }\n }\n ```\n\n \n \n\n **1. Using the Component:**\n\n ```tsx\n import { MarkdownRenderer } from \"solid-intlayer/markdown\";\n\n \n {\"# My Title\"}\n \n ```\n\n **2. Using the Hook:**\n\n ```tsx\n import { useMarkdownRenderer } from \"solid-intlayer/markdown\";\n\n const render = useMarkdownRenderer();\n\n return
    {render(\"# My Title\")}
    ;\n ```\n\n **3. Using the Utility Function:**\n\n ```tsx\n import { renderMarkdown } from \"solid-intlayer/markdown\";\n\n return
    {renderMarkdown(\"# My Title\")}
    ;\n ```\n\n
    \n \n\n **1. Using the Component:**\n\n ```tsx\n import { MarkdownRenderer } from \"preact-intlayer/markdown\";\n\n \n {\"# My Title\"}\n \n ```\n\n **2. Using the Hook:**\n\n ```tsx\n import { useMarkdownRenderer } from \"preact-intlayer/markdown\";\n\n const render = useMarkdownRenderer();\n\n return
    {render(\"# My Title\")}
    ;\n ```\n\n **3. Using the Utility Function:**\n\n ```tsx\n import { renderMarkdown } from \"preact-intlayer/markdown\";\n\n return
    {renderMarkdown(\"# My Title\")}
    ;\n ```\n\n
    \n\n\n#### Examples: HTML Rendering Tools\n\n\n \n\n **1. Using the Component:**\n\n ```tsx\n import { HTMLRenderer } from \"react-intlayer/html\";\n\n

    {children}

    \n }}\n >\n {\"

    Hello World

    \"}\n \n ```\n\n **2. Using the Hook:**\n\n ```tsx\n import { useHTMLRenderer } from \"react-intlayer/html\";\n\n const renderHTML = useHTMLRenderer({\n components: {\n strong: ({ children }) => {children}\n }\n });\n\n return
    {renderHTML(\"

    Hello World

    \")}
    ;\n ```\n\n **3. Using the Utility Function:**\n\n ```tsx\n import { renderHTML } from \"react-intlayer/html\";\n\n const html = renderHTML(\"

    Hello World

    \");\n ```\n\n
    \n \n\n **1. Using the Component:**\n\n ```vue\n \n\n \n ```\n\n \n \n\n **1. Using the Component:**\n\n ```svelte\n \n\n Hello World

    \" />\n ```\n\n **2. Using the Hook:**\n\n ```svelte\n \n\n {@html render(\"

    Hello World

    \")}\n ```\n\n **3. Using the Utility Function:**\n\n ```svelte\n \n\n {@html renderHTML(\"

    Hello World

    \")}\n ```\n\n
    \n \n\n **1. Direct Usage:**\n\n In Angular, you can use the standard `[innerHTML]` binding.\n\n ```html\n
    Hello World

    '\">
    \n ```\n\n
    \n \n\n **1. Using the Component:**\n\n ```tsx\n import { HTMLRenderer } from \"solid-intlayer/html\";\n\n \n {\"

    Hello World

    \"}\n
    \n ```\n\n **2. Using the Hook:**\n\n ```tsx\n import { useHTMLRenderer } from \"solid-intlayer/html\";\n\n const render = useHTMLRenderer();\n\n return
    {render(\"

    Hello World

    \")}
    ;\n ```\n\n **3. Using the Utility Function:**\n\n ```tsx\n import { renderHTML } from \"solid-intlayer/html\";\n\n return
    {renderHTML(\"

    Hello World

    \")}
    ;\n ```\n\n
    \n \n\n **1. Using the Component:**\n\n ```tsx\n import { HTMLRenderer } from \"preact-intlayer/html\";\n\n \n {\"

    Hello World

    \"}\n
    \n ```\n\n **2. Using the Hook:**\n\n ```tsx\n import { useHTMLRenderer } from \"preact-intlayer/html\";\n\n const render = useHTMLRenderer();\n\n return
    {render(\"

    Hello World

    \")}
    ;\n ```\n\n **3. Using the Utility Function:**\n\n ```tsx\n import { renderHTML } from \"preact-intlayer/html\";\n\n return
    {renderHTML(\"

    Hello World

    \")}
    ;\n ```\n\n
    \n
    \n\nFor more details, see the [HTML Content Documentation](/doc/concept/content/html) and [Markdown Documentation](/doc/concept/content/markdown).\n\n---\n\n## Custom URL Rewrites\n\nIntlayer v8 introduces support for **Custom URL Rewrites**, allowing you to define locale-specific paths that differ from the standard `/locale/path` structure. This is a powerful feature for improving local SEO and providing a more natural user experience for non-English speakers.\n\n**Key enhancements in v8:**\n\n- **Framework Formatters**: New `nextjsRewrite`, `svelteKitRewrite`, `reactRouterRewrite`, `vueRouterRewrite`, `solidRouterRewrite`, `tanstackRouterRewrite`, `nuxtRewrite`, and `viteRewrite` to provide idiomatic pattern syntax for each router.\n- **`useRewriteURL` Hook**: A new client-side hook that silently corrects the address bar to the \"pretty\" localized URL without triggering router navigations.\n- **Automatic SEO Redirects**: Built-in proxies now automatically redirect users from manually typed canonical paths (e.g., `/fr/about`) to their prettier localized versions (e.g., `/fr/a-propos`).\n\n**Example Configuration:**\n\n\n \n\n ```typescript fileName=\"intlayer.config.ts\"\n import { Locales, type IntlayerConfig } from \"intlayer\";\n import { nextjsRewrite } from \"intlayer/routing\";\n\n const config: IntlayerConfig = {\n internationalization: {\n locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],\n defaultLocale: Locales.ENGLISH,\n },\n routing: {\n mode: \"prefix-no-default\",\n rewrite: nextjsRewrite({\n \"/[locale]/about\": {\n fr: \"/[locale]/a-propos\",\n es: \"/[locale]/acerca-de\",\n },\n \"/[locale]/products/[id]\": {\n fr: \"/[locale]/produits/[id]\",\n es: \"/[locale]/productos/[id]\",\n },\n }),\n },\n };\n\n export default config;\n ```\n\n \n \n\n ```typescript fileName=\"intlayer.config.ts\"\n import { Locales, type IntlayerConfig } from \"intlayer\";\n import { reactRouterRewrite } from \"intlayer/routing\";\n\n const config: IntlayerConfig = {\n internationalization: {\n locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],\n defaultLocale: Locales.ENGLISH,\n },\n routing: {\n mode: \"prefix-all\",\n rewrite: reactRouterRewrite({\n \"/:locale/about\": {\n fr: \"/:locale/a-propos\",\n es: \"/:locale/acerca-de\",\n },\n \"/:locale/products/:id\": {\n fr: \"/:locale/produits/:id\",\n es: \"/:locale/productos/:id\",\n },\n }),\n },\n };\n\n export default config;\n ```\n\n \n \n\n ```typescript fileName=\"intlayer.config.ts\"\n import { Locales, type IntlayerConfig } from \"intlayer\";\n import { viteRewrite } from \"intlayer/routing\";\n\n const config: IntlayerConfig = {\n internationalization: {\n locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],\n defaultLocale: Locales.ENGLISH,\n },\n routing: {\n mode: \"prefix-all\",\n rewrite: viteRewrite({\n \"/:locale/about\": {\n fr: \"/:locale/a-propos\",\n es: \"/:locale/acerca-de\",\n },\n \"/:locale/products/:id\": {\n fr: \"/:locale/produits/:id\",\n es: \"/:locale/productos/:id\",\n },\n }),\n },\n };\n\n export default config;\n ```\n\n \n \n\n ```typescript fileName=\"intlayer.config.ts\"\n import { Locales, type IntlayerConfig } from \"intlayer\";\n import { nuxtRewrite } from \"intlayer/routing\";\n\n const config: IntlayerConfig = {\n internationalization: {\n locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],\n defaultLocale: Locales.ENGLISH,\n },\n routing: {\n mode: \"prefix-all\",\n rewrite: nuxtRewrite({\n \"/[locale]/about\": {\n fr: \"/[locale]/a-propos\",\n es: \"/[locale]/acerca-de\",\n },\n \"/[locale]/products/[id]\": {\n fr: \"/[locale]/produits/[id]\",\n es: \"/[locale]/productos/[id]\",\n },\n }),\n },\n };\n\n export default config;\n ```\n\n \n \n\n ```typescript fileName=\"intlayer.config.ts\"\n import { Locales, type IntlayerConfig } from \"intlayer\";\n import { svelteKitRewrite } from \"intlayer/routing\";\n\n const config: IntlayerConfig = {\n internationalization: {\n locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],\n defaultLocale: Locales.ENGLISH,\n },\n routing: {\n mode: \"prefix-all\",\n rewrite: svelteKitRewrite({\n \"/[locale]/about\": {\n fr: \"/[locale]/a-propos\",\n es: \"/[locale]/acerca-de\",\n },\n \"/[locale]/products/[id]\": {\n fr: \"/[locale]/produits/[id]\",\n es: \"/[locale]/productos/[id]\",\n },\n }),\n },\n };\n\n export default config;\n ```\n\n \n\n\nThis feature is supported out-of-the-box in **Next.js** and **Vite** through the Intlayer proxies, and can be easily integrated into other routers like **TanStack Router**, **React Router**, **Vue Router**, **SvelteKit**, and **Solid Router**.\n\nFor more information and integration guides, see the [Custom URL Rewrites Documentation](/doc/concept/custom_url_rewrites).\n\n---\n\n### Enhanced Insertion Values\n\nIn v8, insertion values can now **accept React elements (or Vue nodes)** in addition to strings and numbers. This allows you to inject rich, interactive components directly into your insertion templates.\n\nIntlayer now robustly handles nested React and Preact nodes within insertions, ensuring that complex UI structures are preserved and rendered correctly.\n\n**Example:**\n\n```typescript fileName=\"src/example.content.ts\"\nimport { insert } from \"intlayer\";\n\nexport default {\n key: \"my-key\",\n content: {\n myInsertion: insert(\"Hi {{name}}\"),\n },\n};\n```\n\n\n \n\n ```tsx\n import { useIntlayer } from \"next-intlayer\";\n\n const { myInsertion } = useIntlayer(\"my-key\");\n\n return (\n
    \n {myInsertion({\n name: 2, // number\n // or\n name: \"John\", // string\n // or\n name: John, // React element\n })}\n
    \n );\n ```\n\n
    \n \n\n ```tsx\n import { useIntlayer } from \"react-intlayer\";\n\n const { myInsertion } = useIntlayer(\"my-key\");\n\n return (\n
    \n {myInsertion({\n name: 2, // number\n // or\n name: \"John\", // string\n // or\n name: John, // React element\n })}\n
    \n );\n ```\n\n
    \n \n\n ```vue\n \n\n \n ```\n\n \n \n\n ```tsx\n import { useIntlayer } from \"preact-intlayer\";\n\n const { myInsertion } = useIntlayer(\"my-key\");\n\n return (\n
    \n {myInsertion({\n name: 2, // number\n // or\n name: \"John\", // string\n // or\n name: John, // Preact element\n })}\n
    \n );\n ```\n\n
    \n \n\n ```tsx\n import { useIntlayer } from \"solid-intlayer\";\n\n const { myInsertion } = useIntlayer(\"my-key\");\n\n return (\n
    \n {myInsertion({\n name: 2, // number\n // or\n name: \"John\", // string\n // or\n name: John, // Solid element\n })}\n
    \n );\n ```\n\n
    \n \n\n ```svelte\n \n\n
    \n {myInsertion({\n name: 2, // number\n // or\n name: \"John\", // string\n })}\n
    \n ```\n\n
    \n \n\n ```typescript\n import { Component } from \"@angular/core\";\n import { useIntlayer } from \"angular-intlayer\";\n\n @Component({\n selector: \"app-insertion-example\",\n template: `\n
    \n {{ content().myInsertion({\n name: 'John'\n }) }}\n
    \n `,\n })\n export class InsertionExampleComponent {\n content = useIntlayer(\"my-key\");\n }\n ```\n\n
    \n
    \n\n## Content Schema Validation\n\nIntlayer v8 introduces schema validation for dictionaries. You can now define reusable validation schemas in your configuration using Zod and apply them to your content files. This ensures your content always adheres to the expected structure, catching errors at build time.\n\n### 1. Define Schemas\n\nDefine your schemas in `intlayer.config.ts`:\n\n```typescript fileName=\"intlayer.config.ts\"\nimport { z } from \"zod\";\n\nexport default {\n schemas: {\n \"seo-metadata\": z.object({\n title: z.string().min(50).max(60),\n description: z.string().min(150).max(160),\n }),\n },\n};\n```\n\n### 2. Apply Schemas to Dictionaries\n\nReference the schema key in your dictionary definition:\n\n```typescript fileName=\"src/example.content.ts\"\nimport { type Dictionary } from \"intlayer\";\n\nconst aboutPageMetaContent = {\n key: \"about-page-meta\",\n schema: \"seo-metadata\", // <--\n content: {\n title: \"About Our Company - Learn More About Us\",\n description: \"Discover our company's mission, values, and team.\",\n },\n} satisfies Dictionary;\n\nexport default aboutPageMetaContent;\n```\n\nIf the content doesn't match the schema (e.g., title is too short), the build process will raise an error.\n\n---\n\n### Enhanced Automatic Content Detection\n\nIn v8, Intlayer intelligently detects Markdown syntax, HTML tags, and variable insertions in your content strings. This means you can often omit helper functions like `md()`, `html()`, or `insert()`.\n\nThis behavior is enabled by default. You can now fine-tune this detection either globally in your `intlayer.config.ts` or per dictionary.\n\n#### Granular Control\n\nYou can enable or disable specific types of transformations:\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n dictionary: {\n // contentAutoTransformation: false (default)\n contentAutoTransformation: {\n markdown: true,\n html: true,\n insertion: false, // Disable automatic insertion detection\n },\n },\n};\n```\n\n**v7 behavior (Manual wrapping):**\n\n```typescript fileName=\"src/example.content.ts\"\nimport { md, insert } from \"intlayer\";\n\nexport default {\n key: \"my-key\",\n content: {\n myMarkdown: md(\"## Hello World\"),\n myInsertion: insert(\"Hi {{name}}\"),\n },\n};\n```\n\n**v8 behavior (Automatic detection):**\n\n```typescript fileName=\"src/example.content.ts\"\nexport default {\n key: \"my-key\",\n contentAutoTransformation: true, // Can also be set by dictionary definition or globally in intlayer.config.ts\n content: {\n myMarkdown: \"## Hello World\", // Automatically detected as Markdown\n myHTML: \"

    Hello World

    \", // Automatically detected as HTML\n myInsertion: \"Hi {{name}}\", // Automatically detected as Insertion\n },\n};\n```\n\nThe underlying JSON result remains the same, preserving the rich type information needed for rendering:\n\n```json\n{\n \"key\": \"my-key\",\n \"content\": {\n \"myMarkdown\": {\n \"nodeType\": \"markdown\",\n \"markdown\": \"## Hello World\"\n },\n \"myHTML\": {\n \"nodeType\": \"html\",\n \"html\": \"

    Hello World

    \"\n },\n \"myInsertion\": {\n \"nodeType\": \"insertion\",\n \"insertion\": \"Hi {{name}}\"\n }\n }\n}\n```\n\n---\n\n## Localization: new `useIntl` hook\n\nA new `useIntl()` hook is now available in React, Next.js and Vue. It provides a locale-bound `Intl` object that automatically uses the current language for formatting numbers, dates, and more, without needing to manually pass the locale.\n\n\n \n\n ```tsx\n import { useIntl } from \"next-intlayer\";\n\n const intl = useIntl();\n\n const formattedPrice = new intl.NumberFormat({\n style: \"currency\",\n currency: \"USD\",\n }).format(123.45);\n ```\n\n \n \n\n ```tsx\n import { useIntl } from \"react-intlayer\";\n\n const intl = useIntl();\n\n const formattedPrice = new intl.NumberFormat({\n style: \"currency\",\n currency: \"USD\",\n }).format(123.45);\n ```\n\n \n \n\n ```vue\n \n ```\n\n \n \n\n ```tsx\n import { useIntl } from \"preact-intlayer\";\n\n const intl = useIntl();\n\n const formattedPrice = new intl.NumberFormat({\n style: \"currency\",\n currency: \"USD\",\n }).format(123.45);\n ```\n\n \n \n\n ```tsx\n import { useIntl } from \"solid-intlayer\";\n\n const intl = useIntl();\n\n const formattedPrice = new intl.NumberFormat({\n style: \"currency\",\n currency: \"USD\",\n }).format(123.45);\n ```\n\n \n \n\n ```svelte\n \n ```\n\n \n \n\n ```typescript\n import { Component, computed } from \"@angular/core\";\n import { useIntl } from \"angular-intlayer\";\n\n @Component({\n selector: \"app-intl-example\",\n template: `
    {{ formattedPrice() }}
    `,\n })\n export class IntlExampleComponent {\n intl = useIntl();\n\n formattedPrice = computed(() =>\n new (this.intl().NumberFormat)({\n style: \"currency\",\n currency: \"USD\",\n }).format(123.45)\n );\n }\n ```\n\n
    \n
    \n\n---\n\n## Tooling: VSCode Extension Enhancements\n\nThe Intlayer VSCode extension receives major updates in v8 to streamline your internationalization workflow:\n\n- **Starting Time**: Performance improvements when opening a project.\n- **Caching**: Enhanced caching layer for near-instant validation and autocompletion.\n- **Unused Keys & Duplicated Keys Detection**: New features to automatically detect **unused keys** and **duplicated keys** across your dictionaries, helping you keep your content clean and efficient.\n\n---\n\n## Compiler Optimizations\n\nIntlayer v8 includes a new caching layer for the Markdown and HTML compiler. This ensures that identical content strings with the same configuration are only parsed once, significantly reducing the overhead during re-renders or when using the same content in multiple places.\n\n\n \n \n ```typescript fileName=\"babel.config.js\"\n const {\n intlayerExtractBabelPlugin,\n intlayerOptimizeBabelPlugin,\n getExtractPluginOptions,\n getOptimizePluginOptions,\n } = require('@intlayer/babel');\n\n module.exports = {\n presets: ['next/babel'],\n plugins: [\n // Extract content from components into dictionaries\n [intlayerExtractBabelPlugin, getExtractPluginOptions()],\n // Optimize imports by replacing useIntlayer with direct dictionary imports\n [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n ],\n };\n ```\n\n \n \n \n ```typescript fileName=\"vite.config.js\"\n import { defineConfig } from 'vite';\n import { intlayer, intlayerCompiler } from \"vite-intlayer\";\n\n export default defineConfig({\n plugins: [intlayer(), intlayerCompiler()],\n });\n ```\n\n> For vue / svelte you will need to install the appropriate compiler package:\n>\n> ```bash\n> # For Vue\n> npm install @intlayer/vue-compiler\n> ```\n>\n> ```bash\n> # For Svelte\n> npm install @intlayer/svelte-compiler\n> ```\n\n \n\n\n---\n\n## Flexibility: Unified Import Mode\n\nThe `live` boolean property has been deprecated in favor of a more comprehensive `importMode` property. This allows for explicit definition of how dictionaries should be loaded: statically, dynamically, or via live sync.\n\n### Modes\n\n- **`static`** (Default): Dictionary is bundled at build time. Best for performance.\n- **`dynamic`**: Dictionary is loaded at runtime (e.g., via JSON fetch or suspense).\n- **`fetch`**: Dictionary is fetched from the CMS/Server at runtime and synchronized.\n\n**Migration:**\n\n| v7 Config | v8 Config |\n| :------------ | :------------------------------------ |\n| `live: true` | `importMode: 'fetch'` |\n| `live: false` | `importMode: 'static'` (or 'dynamic') |\n\nNote: In Intlayer v8, the `importMode` property has been moved from the `build` configuration to the `dictionary` configuration in `intlayer.config.ts`. This allows you to define a default import mode for all your dictionaries while still being able to override it on a per-dictionary basis.\n\n**Global Configuration Example:**\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n dictionary: {\n importMode: \"dynamic\", // Global default\n },\n // ...\n};\n```\n\n**Dictionary Example:**\n\n```typescript fileName=\"src/example.content.ts\"\nexport default {\n key: 'my-key',\n importMode: \"fetch\", // Overrides global config\n content: { ... }\n}\n```\n\n---\n\n## Dictionary Location Control\n\nv8 introduces the `location` property to explicitly manage where dictionaries live and how they synchronize. This is particularly useful for hybrid workflows involving both local files and remote CMS content.\n\n### Options\n\n- **`local`**: The dictionary exists only locally. It will not be pushed to the remote CMS.\n- **`remote`**: The dictionary is managed remotely. Once pushed on the CMS, it will be detached from the local one. The remote dictionary will be pulled from the CMS.\n- **`local_and_remote`**: The dictionary exists in both places. Local changes are pushed, and remote changes are pulled (synchronized).\n\n**Example:**\n\n```typescript fileName=\"src/example.content.ts\"\nexport default {\n key: 'my-key',\n location: \"local\", // Keep this dictionary local-only\n content: { ... }\n}\n```\n\n---\n\n## System Configuration Separation\n\nIntlayer v8 separates the configuration of content sources from internal system and output paths. This declutters the `content` property and makes it clear which settings are intended for user management vs. those that are managed by the Intlayer system.\n\nThe following properties have been moved from `content` to a new `system` property in `intlayer.config.ts`:\n\n- `dictionariesDir`\n- `moduleAugmentationDir`\n- `unmergedDictionariesDir`\n- `typesDir`\n- `mainDir`\n- `configDir`\n- `cacheDir`\n- `outputFilesPatternWithPath`\n\n**v7 behavior:**\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n content: {\n contentDir: [\"src\"],\n dictionariesDir: \".intlayer/dictionary\", // Mixed with source config\n },\n};\n```\n\n**v8 behavior:**\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n content: {\n contentDir: [\"src\"],\n },\n system: {\n dictionariesDir: \".intlayer/dictionary\", // Clearly separated\n },\n};\n```\n\n---\n\n## Content and Code Directory Separation\n\nIntlayer v8 separates the configuration for content definition files from the configuration for code transformation. This allows for more precise watching and scanning, improving build performance.\n\nPreviously, `contentDir` was used for both watching `.content.*` files and scanning code for `useIntlayer` calls. Now:\n\n- **`contentDir`**: Specifically for your content declaration files.\n- **`codeDir`**: Specifically for your application code that needs transformation (e.g., pruning, optimization).\n\n**Migration:**\n\nIf you previously had `contentDir` set, Intlayer v8 will use it as the default for `codeDir` as well, but will log a warning. You should explicitly define `codeDir` in your configuration.\n\n**v7 behavior:**\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n content: {\n contentDir: [\"src\", \"@packages/design-system\"], // Used for both content and code\n },\n};\n```\n\n**v8 behavior:**\n\n```typescript fileName=\"intlayer.config.ts\"\nexport default {\n content: {\n contentDir: [\"src/content\", \"@packages/design-system\"], // Only watch for src/content/*.content.* files here and @packages/design-system/dist/*.content.* files\n codeDir: [\"src\", \"@packages/design-system\"], // Only scan for code transformation here and @packages/design-system/src/*.content.* files\n },\n};\n```\n\n---\n\n## Framework: Svelte Improvements\n\nMarkdown and HTML content in Svelte now automatically parse to HTML when stringified. This makes it much easier to use with Svelte's `{@html}` syntax, as you can now simply pass the content node directly.\n\n---\n\n## Migration notes from v7\n\n### Configuration Changes\n\n- **`live` property**: The `live` property in dictionaries is removed. Use `importMode: 'fetch'` instead.\n- **importMode**: The `build.importMode` property in configuration has been deprecated. Use `dictionary.importMode` instead.\n- **`contentDir` and `codeDir`**: `contentDir` is now specifically for content files. A new `codeDir` property has been added for code transformation. If `codeDir` is not set, Intlayer will fallback to `contentDir` and log a warning.\n- **Schema Validation**: To use the new `schema` feature, ensure you have `zod` installed in your project.\n\n---\n\n## Useful links\n\n- [Configuration Reference](/doc/concept/configuration)\n- [Content File Documentation](/doc/concept/content)\n- [HTML Content Documentation](/doc/concept/content/html)\n- [Markdown Content Documentation](/doc/concept/content/markdown)\n- [Custom URL Rewrites Documentation](/doc/concept/custom_url_rewrites)\n","about":"Discover what's new in Intlayer v8. Major improvements in developer experience, content validation, and dictionary management.","url":"https://intlayer.org/doc/releases/v8","datePublished":"22-09-2025","dateModified":"26-01-2026","keywords":"Intlayer, CMS, Developer Experience, Features, React, Next.js, JavaScript, TypeScript","license":"https://raw.githubusercontent.com/aymericzip/intlayer/refs/heads/main/LICENSE","audience":{"@type":"Audience","audienceType":"Developers, Content Managers"}}
    Creation:2025-09-22Last update:2026-01-26
    Watch the video tutorial

    This page has a video tutorial available.

    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

    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

    New Intlayer v8 - What's new?

    Welcome to Intlayer v8! This release focuses on enhancing developer experience with automatic content detection, ensuring data integrity with schema validation, and providing more control over dictionary management.

    www.youtube.com

    Table of contents


    Rich Content Evolution: Markdown & HTML

    Intlayer v8 brings major improvements to how rich content is handled, introducing HTML nodes (which didn't exist in v7) and unifying the API with Markdown nodes (which existed in v7 but have been enhanced).

    The Unified .use() API

    We introduced the .use() method for both Markdown and HTML nodes. This method allows you to customize the HTML tags or components used during rendering.

    • Component Replacement: You can easily replace HTML tags or custom components with your own framework components (e.g., replace <a> with NextLink or <CustomCmp> with a React component).
    • Type Safety: All functions for providing components are fully typed, ensuring you receive the correct props.

    Default Rendering Behavior

    In v7, if no provider was defined, Markdown nodes were rendered as raw strings, often requiring external libraries to parse them.

    In v8, Intlayer includes its own internal Markdown parser. By default, Markdown nodes are now rendered directly as HTML without needing any external libraries.

    New Renderer & Provider Utilities

    We have introduced new standalone renderer functions and components to give you more control outside of the standard useIntlayer flow.

    • Markdown: MarkdownRenderer, useMarkdownRenderer, renderMarkdown. (Note: MarkdownProvider existed in v7 but now integrates with these new tools).
    • HTML: HTMLRenderer, useHTMLRenderer, renderHTML, HTMLProvider.

    Examples: Markdown Rendering Tools

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { MarkdownRenderer } from "react-intlayer/markdown";<MarkdownRenderer  forceBlock={true}  components={{    h1: ({ children }) => <h1 className="text-2xl">{children}</h1>  }}>  {"# My Title"}</MarkdownRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useMarkdownRenderer } from "react-intlayer/markdown";const renderMarkdown = useMarkdownRenderer({  components: {    h1: ({ children }) => <h1 className="text-red-500">{children}</h1>  }});return <div>{renderMarkdown("# My Title")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderMarkdown } from "react-intlayer/markdown";const html = renderMarkdown("# My Title", {  forceBlock: true});

    1. Using the Component:

    vue
    Copy code

    Copy the code to the clipboard

    <script setup>import { MarkdownRenderer } from "vue-intlayer/markdown";</script><template>  <MarkdownRenderer :forceBlock="true" content="# My Title" /></template>

    1. Using the Component:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { MarkdownRenderer } from "svelte-intlayer/markdown";</script><MarkdownRenderer forceBlock={true} value="# My Title" />

    2. Using the Hook:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { useMarkdownRenderer } from "svelte-intlayer/markdown";  const render = useMarkdownRenderer();</script>{@html render("# My Title")}

    3. Using the Utility Function:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { renderMarkdown } from "svelte-intlayer/markdown";</script>{@html renderMarkdown("# My Title")}

    1. Using the Service:

    typescript
    Copy code

    Copy the code to the clipboard

    import { Component } from "@angular/core";import { IntlayerMarkdownService } from "angular-intlayer";@Component({ ... })export class MyComponent {  constructor(private markdownService: IntlayerMarkdownService) {}  render(markdown: string) {    return this.markdownService.renderMarkdown(markdown);  }}

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { MarkdownRenderer } from "solid-intlayer/markdown";<MarkdownRenderer forceBlock={true}>  {"# My Title"}</MarkdownRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useMarkdownRenderer } from "solid-intlayer/markdown";const render = useMarkdownRenderer();return <div>{render("# My Title")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderMarkdown } from "solid-intlayer/markdown";return <div>{renderMarkdown("# My Title")}</div>;

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { MarkdownRenderer } from "preact-intlayer/markdown";<MarkdownRenderer forceBlock={true}>  {"# My Title"}</MarkdownRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useMarkdownRenderer } from "preact-intlayer/markdown";const render = useMarkdownRenderer();return <div>{render("# My Title")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderMarkdown } from "preact-intlayer/markdown";return <div>{renderMarkdown("# My Title")}</div>;

    Examples: HTML Rendering Tools

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { HTMLRenderer } from "react-intlayer/html";<HTMLRenderer  components={{    p: ({ children }) => <p className="mb-4">{children}</p>  }}>  {"<p>Hello World</p>"}</HTMLRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useHTMLRenderer } from "react-intlayer/html";const renderHTML = useHTMLRenderer({  components: {    strong: ({ children }) => <b className="font-bold">{children}</b>  }});return <div>{renderHTML("<p>Hello <strong>World</strong></p>")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderHTML } from "react-intlayer/html";const html = renderHTML("<p>Hello World</p>");

    1. Using the Component:

    vue
    Copy code

    Copy the code to the clipboard

    <script setup>import { HTMLRenderer } from "vue-intlayer/html";</script><template>  <HTMLRenderer content="<p>Hello World</p>" /></template>

    1. Using the Component:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { HTMLRenderer } from "svelte-intlayer/html";</script><HTMLRenderer value="<p>Hello World</p>" />

    2. Using the Hook:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { useHTMLRenderer } from "svelte-intlayer/html";  const render = useHTMLRenderer();</script>{@html render("<p>Hello World</p>")}

    3. Using the Utility Function:

    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { renderHTML } from "svelte-intlayer/html";</script>{@html renderHTML("<p>Hello World</p>")}

    1. Direct Usage:

    In Angular, you can use the standard [innerHTML] binding.

    html
    Copy code

    Copy the code to the clipboard

    <div [innerHTML]="'<p>Hello World</p>'"></div>

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { HTMLRenderer } from "solid-intlayer/html";<HTMLRenderer>  {"<p>Hello World</p>"}</HTMLRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useHTMLRenderer } from "solid-intlayer/html";const render = useHTMLRenderer();return <div>{render("<p>Hello World</p>")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderHTML } from "solid-intlayer/html";return <div>{renderHTML("<p>Hello World</p>")}</div>;

    1. Using the Component:

    tsx
    Copy code

    Copy the code to the clipboard

    import { HTMLRenderer } from "preact-intlayer/html";<HTMLRenderer>  {"<p>Hello World</p>"}</HTMLRenderer>

    2. Using the Hook:

    tsx
    Copy code

    Copy the code to the clipboard

    import { useHTMLRenderer } from "preact-intlayer/html";const render = useHTMLRenderer();return <div>{render("<p>Hello World</p>")}</div>;

    3. Using the Utility Function:

    tsx
    Copy code

    Copy the code to the clipboard

    import { renderHTML } from "preact-intlayer/html";return <div>{renderHTML("<p>Hello World</p>")}</div>;

    For more details, see the HTML Content Documentation and Markdown Documentation.


    Custom URL Rewrites

    Intlayer v8 introduces support for Custom URL Rewrites, allowing you to define locale-specific paths that differ from the standard /locale/path structure. This is a powerful feature for improving local SEO and providing a more natural user experience for non-English speakers.

    Key enhancements in v8:

    • Framework Formatters: New nextjsRewrite, svelteKitRewrite, reactRouterRewrite, vueRouterRewrite, solidRouterRewrite, tanstackRouterRewrite, nuxtRewrite, and viteRewrite to provide idiomatic pattern syntax for each router.
    • useRewriteURL Hook: A new client-side hook that silently corrects the address bar to the "pretty" localized URL without triggering router navigations.
    • Automatic SEO Redirects: Built-in proxies now automatically redirect users from manually typed canonical paths (e.g., /fr/about) to their prettier localized versions (e.g., /fr/a-propos).

    Example Configuration:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";import { nextjsRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-no-default",    rewrite: nextjsRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;
    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";import { reactRouterRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: reactRouterRewrite({      "/:locale/about": {        fr: "/:locale/a-propos",        es: "/:locale/acerca-de",      },      "/:locale/products/:id": {        fr: "/:locale/produits/:id",        es: "/:locale/productos/:id",      },    }),  },};export default config;
    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";import { viteRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: viteRewrite({      "/:locale/about": {        fr: "/:locale/a-propos",        es: "/:locale/acerca-de",      },      "/:locale/products/:id": {        fr: "/:locale/produits/:id",        es: "/:locale/productos/:id",      },    }),  },};export default config;
    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";import { nuxtRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: nuxtRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;
    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { Locales, type IntlayerConfig } from "intlayer";import { svelteKitRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: svelteKitRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;

    This feature is supported out-of-the-box in Next.js and Vite through the Intlayer proxies, and can be easily integrated into other routers like TanStack Router, React Router, Vue Router, SvelteKit, and Solid Router.

    For more information and integration guides, see the Custom URL Rewrites Documentation.


    Enhanced Insertion Values

    In v8, insertion values can now accept React elements (or Vue nodes) in addition to strings and numbers. This allows you to inject rich, interactive components directly into your insertion templates.

    Intlayer now robustly handles nested React and Preact nodes within insertions, ensuring that complex UI structures are preserved and rendered correctly.

    Example:

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    import { insert } from "intlayer";export default {  key: "my-key",  content: {    myInsertion: insert("Hi {{name}}"),  },};
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntlayer } from "next-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // React element    })}  </div>);
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntlayer } from "react-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // React element    })}  </div>);
    vue
    Copy code

    Copy the code to the clipboard

    <script setup>import { h } from "vue";import { useIntlayer } from "vue-intlayer";const { myInsertion } = useIntlayer("my-key");</script><template>  <div>    <component      :is="myInsertion({        name: 2,        // or        name: 'John',        // or        name: h('span', 'John'),      })"    />  </div></template>
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntlayer } from "preact-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // Preact element    })}  </div>);
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntlayer } from "solid-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // Solid element    })}  </div>);
    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { useIntlayer } from "svelte-intlayer";  const { myInsertion } = useIntlayer("my-key");</script><div>  {myInsertion({    name: 2, // number    // or    name: "John", // string  })}</div>
    typescript
    Copy code

    Copy the code to the clipboard

    import { Component } from "@angular/core";import { useIntlayer } from "angular-intlayer";@Component({  selector: "app-insertion-example",  template: `    <div>      {{ content().myInsertion({        name: 'John'      }) }}    </div>  `,})export class InsertionExampleComponent {  content = useIntlayer("my-key");}

    Content Schema Validation

    Intlayer v8 introduces schema validation for dictionaries. You can now define reusable validation schemas in your configuration using Zod and apply them to your content files. This ensures your content always adheres to the expected structure, catching errors at build time.

    1. Define Schemas

    Define your schemas in intlayer.config.ts:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    import { z } from "zod";export default {  schemas: {    "seo-metadata": z.object({      title: z.string().min(50).max(60),      description: z.string().min(150).max(160),    }),  },};

    2. Apply Schemas to Dictionaries

    Reference the schema key in your dictionary definition:

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    import { type Dictionary } from "intlayer";const aboutPageMetaContent = {  key: "about-page-meta",  schema: "seo-metadata", // <--  content: {    title: "About Our Company - Learn More About Us",    description: "Discover our company's mission, values, and team.",  },} satisfies Dictionary;export default aboutPageMetaContent;

    If the content doesn't match the schema (e.g., title is too short), the build process will raise an error.


    Enhanced Automatic Content Detection

    In v8, Intlayer intelligently detects Markdown syntax, HTML tags, and variable insertions in your content strings. This means you can often omit helper functions like md(), html(), or insert().

    This behavior is enabled by default. You can now fine-tune this detection either globally in your intlayer.config.ts or per dictionary.

    Granular Control

    You can enable or disable specific types of transformations:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  dictionary: {    // contentAutoTransformation: false (default)    contentAutoTransformation: {      markdown: true,      html: true,      insertion: false, // Disable automatic insertion detection    },  },};

    v7 behavior (Manual wrapping):

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    import { md, insert } from "intlayer";export default {  key: "my-key",  content: {    myMarkdown: md("## Hello World"),    myInsertion: insert("Hi {{name}}"),  },};

    v8 behavior (Automatic detection):

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    export default {  key: "my-key",  contentAutoTransformation: true, // Can also be set by dictionary definition or globally in intlayer.config.ts  content: {    myMarkdown: "## Hello World", // Automatically detected as Markdown    myHTML: "<p>Hello World</p>", // Automatically detected as HTML    myInsertion: "Hi {{name}}", // Automatically detected as Insertion  },};

    The underlying JSON result remains the same, preserving the rich type information needed for rendering:

    json
    Copy code

    Copy the code to the clipboard

    {  "key": "my-key",  "content": {    "myMarkdown": {      "nodeType": "markdown",      "markdown": "## Hello World"    },    "myHTML": {      "nodeType": "html",      "html": "<p>Hello World</p>"    },    "myInsertion": {      "nodeType": "insertion",      "insertion": "Hi {{name}}"    }  }}

    Localization: new useIntl hook

    A new useIntl() hook is now available in React, Next.js and Vue. It provides a locale-bound Intl object that automatically uses the current language for formatting numbers, dates, and more, without needing to manually pass the locale.

    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntl } from "next-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntl } from "react-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
    vue
    Copy code

    Copy the code to the clipboard

    <script setup>import { useIntl } from "vue-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);</script>
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntl } from "preact-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
    tsx
    Copy code

    Copy the code to the clipboard

    import { useIntl } from "solid-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
    svelte
    Copy code

    Copy the code to the clipboard

    <script>  import { useIntl } from "svelte-intlayer";  const intl = useIntl();  const formattedPrice = new intl.NumberFormat({    style: "currency",    currency: "USD",  }).format(123.45);</script>
    typescript
    Copy code

    Copy the code to the clipboard

    import { Component, computed } from "@angular/core";import { useIntl } from "angular-intlayer";@Component({  selector: "app-intl-example",  template: `<div>{{ formattedPrice() }}</div>`,})export class IntlExampleComponent {  intl = useIntl();  formattedPrice = computed(() =>    new (this.intl().NumberFormat)({      style: "currency",      currency: "USD",    }).format(123.45)  );}

    Tooling: VSCode Extension Enhancements

    The Intlayer VSCode extension receives major updates in v8 to streamline your internationalization workflow:

    • Starting Time: Performance improvements when opening a project.
    • Caching: Enhanced caching layer for near-instant validation and autocompletion.
    • Unused Keys & Duplicated Keys Detection: New features to automatically detect unused keys and duplicated keys across your dictionaries, helping you keep your content clean and efficient.

    Compiler Optimizations

    Intlayer v8 includes a new caching layer for the Markdown and HTML compiler. This ensures that identical content strings with the same configuration are only parsed once, significantly reducing the overhead during re-renders or when using the same content in multiple places.

    babel.config.js
    Copy code

    Copy the code to the clipboard

      const {  intlayerExtractBabelPlugin,  intlayerOptimizeBabelPlugin,  getExtractPluginOptions,  getOptimizePluginOptions,} = require('@intlayer/babel');module.exports = {  presets: ['next/babel'],  plugins: [    // Extract content from components into dictionaries    [intlayerExtractBabelPlugin, getExtractPluginOptions()],    // Optimize imports by replacing useIntlayer with direct dictionary imports    [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],  ],};
    vite.config.js
    Copy code

    Copy the code to the clipboard

     import { defineConfig } from 'vite'; import { intlayer, intlayerCompiler } from "vite-intlayer"; export default defineConfig({   plugins: [intlayer(), intlayerCompiler()], }); 

    For vue / svelte you will need to install the appropriate compiler package:

    bash
    Copy code

    Copy the code to the clipboard

    # For Vuenpm install @intlayer/vue-compiler
    bash
    Copy code

    Copy the code to the clipboard

    # For Sveltenpm install @intlayer/svelte-compiler

    Flexibility: Unified Import Mode

    The live boolean property has been deprecated in favor of a more comprehensive importMode property. This allows for explicit definition of how dictionaries should be loaded: statically, dynamically, or via live sync.

    Modes

    • static (Default): Dictionary is bundled at build time. Best for performance.
    • dynamic: Dictionary is loaded at runtime (e.g., via JSON fetch or suspense).
    • fetch: Dictionary is fetched from the CMS/Server at runtime and synchronized.

    Migration:

    Show all table content

    Open the table in a modal to view all data content clearly

    v7 Config v8 Config
    live: true importMode: 'fetch'
    live: false importMode: 'static' (or 'dynamic')

    Note: In Intlayer v8, the importMode property has been moved from the build configuration to the dictionary configuration in intlayer.config.ts. This allows you to define a default import mode for all your dictionaries while still being able to override it on a per-dictionary basis.

    Global Configuration Example:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  dictionary: {    importMode: "dynamic", // Global default  },  // ...};

    Dictionary Example:

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    export default {    key: 'my-key',    importMode: "fetch", // Overrides global config    content: { ... }}

    Dictionary Location Control

    v8 introduces the location property to explicitly manage where dictionaries live and how they synchronize. This is particularly useful for hybrid workflows involving both local files and remote CMS content.

    Options

    • local: The dictionary exists only locally. It will not be pushed to the remote CMS.
    • remote: The dictionary is managed remotely. Once pushed on the CMS, it will be detached from the local one. The remote dictionary will be pulled from the CMS.
    • local_and_remote: The dictionary exists in both places. Local changes are pushed, and remote changes are pulled (synchronized).

    Example:

    src/example.content.ts
    Copy code

    Copy the code to the clipboard

    export default {    key: 'my-key',    location: "local", // Keep this dictionary local-only    content: { ... }}

    System Configuration Separation

    Intlayer v8 separates the configuration of content sources from internal system and output paths. This declutters the content property and makes it clear which settings are intended for user management vs. those that are managed by the Intlayer system.

    The following properties have been moved from content to a new system property in intlayer.config.ts:

    • dictionariesDir
    • moduleAugmentationDir
    • unmergedDictionariesDir
    • typesDir
    • mainDir
    • configDir
    • cacheDir
    • outputFilesPatternWithPath

    v7 behavior:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  content: {    contentDir: ["src"],    dictionariesDir: ".intlayer/dictionary", // Mixed with source config  },};

    v8 behavior:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  content: {    contentDir: ["src"],  },  system: {    dictionariesDir: ".intlayer/dictionary", // Clearly separated  },};

    Content and Code Directory Separation

    Intlayer v8 separates the configuration for content definition files from the configuration for code transformation. This allows for more precise watching and scanning, improving build performance.

    Previously, contentDir was used for both watching .content.* files and scanning code for useIntlayer calls. Now:

    • contentDir: Specifically for your content declaration files.
    • codeDir: Specifically for your application code that needs transformation (e.g., pruning, optimization).

    Migration:

    If you previously had contentDir set, Intlayer v8 will use it as the default for codeDir as well, but will log a warning. You should explicitly define codeDir in your configuration.

    v7 behavior:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  content: {    contentDir: ["src", "@packages/design-system"], // Used for both content and code  },};

    v8 behavior:

    intlayer.config.ts
    Copy code

    Copy the code to the clipboard

    export default {  content: {    contentDir: ["src/content", "@packages/design-system"], // Only watch for src/content/*.content.* files here and @packages/design-system/dist/*.content.* files    codeDir: ["src", "@packages/design-system"], // Only scan for code transformation here and @packages/design-system/src/*.content.* files  },};

    Framework: Svelte Improvements

    Markdown and HTML content in Svelte now automatically parse to HTML when stringified. This makes it much easier to use with Svelte's {@html} syntax, as you can now simply pass the content node directly.


    Migration notes from v7

    Configuration Changes

    • live property: The live property in dictionaries is removed. Use importMode: 'fetch' instead.
    • importMode: The build.importMode property in configuration has been deprecated. Use dictionary.importMode instead.
    • contentDir and codeDir: contentDir is now specifically for content files. A new codeDir property has been added for code transformation. If codeDir is not set, Intlayer will fallback to contentDir and log a warning.
    • Schema Validation: To use the new schema feature, ensure you have zod installed in your project.

    Useful links

    • Configuration Reference
    • Content File Documentation
    • HTML Content Documentation
    • Markdown Content Documentation
    • Custom URL Rewrites Documentation
    Agent skills
    v7
    Alt+→

    In 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.

      import { MarkdownRenderer } from "react-intlayer/markdown";<MarkdownRenderer  forceBlock={true}  components={{    h1: ({ children }) => <h1 className="text-2xl">{children}</h1>  }}>  {"# My Title"}</MarkdownRenderer>
      import { useMarkdownRenderer } from "react-intlayer/markdown";const renderMarkdown = useMarkdownRenderer({  components: {    h1: ({ children }) => <h1 className="text-red-500">{children}</h1>  }});return <div>{renderMarkdown("# My Title")}</div>;
      import { renderMarkdown } from "react-intlayer/markdown";const html = renderMarkdown("# My Title", {  forceBlock: true});
      <script setup>import { MarkdownRenderer } from "vue-intlayer/markdown";</script><template>  <MarkdownRenderer :forceBlock="true" content="# My Title" /></template>
      <script>  import { MarkdownRenderer } from "svelte-intlayer/markdown";</script><MarkdownRenderer forceBlock={true} value="# My Title" />
      <script>  import { useMarkdownRenderer } from "svelte-intlayer/markdown";  const render = useMarkdownRenderer();</script>{@html render("# My Title")}
      <script>  import { renderMarkdown } from "svelte-intlayer/markdown";</script>{@html renderMarkdown("# My Title")}
      import { Component } from "@angular/core";import { IntlayerMarkdownService } from "angular-intlayer";@Component({ ... })export class MyComponent {  constructor(private markdownService: IntlayerMarkdownService) {}  render(markdown: string) {    return this.markdownService.renderMarkdown(markdown);  }}
      import { MarkdownRenderer } from "solid-intlayer/markdown";<MarkdownRenderer forceBlock={true}>  {"# My Title"}</MarkdownRenderer>
      import { useMarkdownRenderer } from "solid-intlayer/markdown";const render = useMarkdownRenderer();return <div>{render("# My Title")}</div>;
      import { renderMarkdown } from "solid-intlayer/markdown";return <div>{renderMarkdown("# My Title")}</div>;
      import { MarkdownRenderer } from "preact-intlayer/markdown";<MarkdownRenderer forceBlock={true}>  {"# My Title"}</MarkdownRenderer>
      import { useMarkdownRenderer } from "preact-intlayer/markdown";const render = useMarkdownRenderer();return <div>{render("# My Title")}</div>;
      import { renderMarkdown } from "preact-intlayer/markdown";return <div>{renderMarkdown("# My Title")}</div>;
      import { HTMLRenderer } from "react-intlayer/html";<HTMLRenderer  components={{    p: ({ children }) => <p className="mb-4">{children}</p>  }}>  {"<p>Hello World</p>"}</HTMLRenderer>
      import { useHTMLRenderer } from "react-intlayer/html";const renderHTML = useHTMLRenderer({  components: {    strong: ({ children }) => <b className="font-bold">{children}</b>  }});return <div>{renderHTML("<p>Hello <strong>World</strong></p>")}</div>;
      import { renderHTML } from "react-intlayer/html";const html = renderHTML("<p>Hello World</p>");
      <script setup>import { HTMLRenderer } from "vue-intlayer/html";</script><template>  <HTMLRenderer content="<p>Hello World</p>" /></template>
      <script>  import { HTMLRenderer } from "svelte-intlayer/html";</script><HTMLRenderer value="<p>Hello World</p>" />
      <script>  import { useHTMLRenderer } from "svelte-intlayer/html";  const render = useHTMLRenderer();</script>{@html render("<p>Hello World</p>")}
      <script>  import { renderHTML } from "svelte-intlayer/html";</script>{@html renderHTML("<p>Hello World</p>")}
      <div [innerHTML]="'<p>Hello World</p>'"></div>
      import { HTMLRenderer } from "solid-intlayer/html";<HTMLRenderer>  {"<p>Hello World</p>"}</HTMLRenderer>
      import { useHTMLRenderer } from "solid-intlayer/html";const render = useHTMLRenderer();return <div>{render("<p>Hello World</p>")}</div>;
      import { renderHTML } from "solid-intlayer/html";return <div>{renderHTML("<p>Hello World</p>")}</div>;
      import { HTMLRenderer } from "preact-intlayer/html";<HTMLRenderer>  {"<p>Hello World</p>"}</HTMLRenderer>
      import { useHTMLRenderer } from "preact-intlayer/html";const render = useHTMLRenderer();return <div>{render("<p>Hello World</p>")}</div>;
      import { renderHTML } from "preact-intlayer/html";return <div>{renderHTML("<p>Hello World</p>")}</div>;
      import { Locales, type IntlayerConfig } from "intlayer";import { nextjsRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-no-default",    rewrite: nextjsRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;
      import { Locales, type IntlayerConfig } from "intlayer";import { reactRouterRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: reactRouterRewrite({      "/:locale/about": {        fr: "/:locale/a-propos",        es: "/:locale/acerca-de",      },      "/:locale/products/:id": {        fr: "/:locale/produits/:id",        es: "/:locale/productos/:id",      },    }),  },};export default config;
      import { Locales, type IntlayerConfig } from "intlayer";import { viteRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: viteRewrite({      "/:locale/about": {        fr: "/:locale/a-propos",        es: "/:locale/acerca-de",      },      "/:locale/products/:id": {        fr: "/:locale/produits/:id",        es: "/:locale/productos/:id",      },    }),  },};export default config;
      import { Locales, type IntlayerConfig } from "intlayer";import { nuxtRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: nuxtRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;
      import { Locales, type IntlayerConfig } from "intlayer";import { svelteKitRewrite } from "intlayer/routing";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],    defaultLocale: Locales.ENGLISH,  },  routing: {    mode: "prefix-all",    rewrite: svelteKitRewrite({      "/[locale]/about": {        fr: "/[locale]/a-propos",        es: "/[locale]/acerca-de",      },      "/[locale]/products/[id]": {        fr: "/[locale]/produits/[id]",        es: "/[locale]/productos/[id]",      },    }),  },};export default config;
      import { insert } from "intlayer";export default {  key: "my-key",  content: {    myInsertion: insert("Hi {{name}}"),  },};
      import { useIntlayer } from "next-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // React element    })}  </div>);
      import { useIntlayer } from "react-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // React element    })}  </div>);
      <script setup>import { h } from "vue";import { useIntlayer } from "vue-intlayer";const { myInsertion } = useIntlayer("my-key");</script><template>  <div>    <component      :is="myInsertion({        name: 2,        // or        name: 'John',        // or        name: h('span', 'John'),      })"    />  </div></template>
      import { useIntlayer } from "preact-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // Preact element    })}  </div>);
      import { useIntlayer } from "solid-intlayer";const { myInsertion } = useIntlayer("my-key");return (  <div>    {myInsertion({      name: 2, // number      // or      name: "John", // string      // or      name: <span>John</span>, // Solid element    })}  </div>);
      <script>  import { useIntlayer } from "svelte-intlayer";  const { myInsertion } = useIntlayer("my-key");</script><div>  {myInsertion({    name: 2, // number    // or    name: "John", // string  })}</div>
      import { Component } from "@angular/core";import { useIntlayer } from "angular-intlayer";@Component({  selector: "app-insertion-example",  template: `    <div>      {{ content().myInsertion({        name: 'John'      }) }}    </div>  `,})export class InsertionExampleComponent {  content = useIntlayer("my-key");}
      import { z } from "zod";export default {  schemas: {    "seo-metadata": z.object({      title: z.string().min(50).max(60),      description: z.string().min(150).max(160),    }),  },};
      import { type Dictionary } from "intlayer";const aboutPageMetaContent = {  key: "about-page-meta",  schema: "seo-metadata", // <--  content: {    title: "About Our Company - Learn More About Us",    description: "Discover our company's mission, values, and team.",  },} satisfies Dictionary;export default aboutPageMetaContent;
      export default {  dictionary: {    // contentAutoTransformation: false (default)    contentAutoTransformation: {      markdown: true,      html: true,      insertion: false, // Disable automatic insertion detection    },  },};
      import { md, insert } from "intlayer";export default {  key: "my-key",  content: {    myMarkdown: md("## Hello World"),    myInsertion: insert("Hi {{name}}"),  },};
      export default {  key: "my-key",  contentAutoTransformation: true, // Can also be set by dictionary definition or globally in intlayer.config.ts  content: {    myMarkdown: "## Hello World", // Automatically detected as Markdown    myHTML: "<p>Hello World</p>", // Automatically detected as HTML    myInsertion: "Hi {{name}}", // Automatically detected as Insertion  },};
      {  "key": "my-key",  "content": {    "myMarkdown": {      "nodeType": "markdown",      "markdown": "## Hello World"    },    "myHTML": {      "nodeType": "html",      "html": "<p>Hello World</p>"    },    "myInsertion": {      "nodeType": "insertion",      "insertion": "Hi {{name}}"    }  }}
      import { useIntl } from "next-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
      import { useIntl } from "react-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
      <script setup>import { useIntl } from "vue-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);</script>
      import { useIntl } from "preact-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
      import { useIntl } from "solid-intlayer";const intl = useIntl();const formattedPrice = new intl.NumberFormat({  style: "currency",  currency: "USD",}).format(123.45);
      <script>  import { useIntl } from "svelte-intlayer";  const intl = useIntl();  const formattedPrice = new intl.NumberFormat({    style: "currency",    currency: "USD",  }).format(123.45);</script>
      import { Component, computed } from "@angular/core";import { useIntl } from "angular-intlayer";@Component({  selector: "app-intl-example",  template: `<div>{{ formattedPrice() }}</div>`,})export class IntlExampleComponent {  intl = useIntl();  formattedPrice = computed(() =>    new (this.intl().NumberFormat)({      style: "currency",      currency: "USD",    }).format(123.45)  );}
        const {  intlayerExtractBabelPlugin,  intlayerOptimizeBabelPlugin,  getExtractPluginOptions,  getOptimizePluginOptions,} = require('@intlayer/babel');module.exports = {  presets: ['next/babel'],  plugins: [    // Extract content from components into dictionaries    [intlayerExtractBabelPlugin, getExtractPluginOptions()],    // Optimize imports by replacing useIntlayer with direct dictionary imports    [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],  ],};
       import { defineConfig } from 'vite'; import { intlayer, intlayerCompiler } from "vite-intlayer"; export default defineConfig({   plugins: [intlayer(), intlayerCompiler()], }); 
      # For Vuenpm install @intlayer/vue-compiler
      # For Sveltenpm install @intlayer/svelte-compiler
      export default {  dictionary: {    importMode: "dynamic", // Global default  },  // ...};
      export default {    key: 'my-key',    importMode: "fetch", // Overrides global config    content: { ... }}
      export default {    key: 'my-key',    location: "local", // Keep this dictionary local-only    content: { ... }}
      export default {  content: {    contentDir: ["src"],    dictionariesDir: ".intlayer/dictionary", // Mixed with source config  },};
      export default {  content: {    contentDir: ["src"],  },  system: {    dictionariesDir: ".intlayer/dictionary", // Clearly separated  },};
      export default {  content: {    contentDir: ["src", "@packages/design-system"], // Used for both content and code  },};
      export default {  content: {    contentDir: ["src/content", "@packages/design-system"], // Only watch for src/content/*.content.* files here and @packages/design-system/dist/*.content.* files    codeDir: ["src", "@packages/design-system"], // Only scan for code transformation here and @packages/design-system/src/*.content.* files  },};