Skip to content

🌐 Storybook Integration Guide ​

πŸ“– Introduction ​

Integrating Nuxt, Storybook, and nuxt-i18n-micro into your project allows you to build a robust, localized application with a component-driven development approach. This guide provides a step-by-step walkthrough on setting up these tools together, including configuration, localization, and Storybook integration.

πŸ›  Nuxt Configuration ​

To enable localization and Storybook in your Nuxt project, configure your nuxt.config.ts file as follows:

πŸ“„ nuxt.config.ts ​

typescript
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-11-01',
  devtools: { enabled: true },
  modules: [
    'nuxt-i18n-micro', // Localization module
    '@nuxtjs/storybook', // Storybook module
  ],

  i18n: {
    locales: [
      { code: 'en', iso: 'en_EN', displayName: 'English' },
      { code: 'de', iso: 'de_DE', displayName: 'German' },
    ],
    strategy: 'prefix',
  },
});

πŸ›  Storybook Configuration ​

To integrate Storybook with Nuxt and nuxt-i18n, configure the .storybook/main.ts file, add mergeTranslations and staticDirs.

πŸ“„ .storybook/main.ts ​

typescript
import type { StorybookConfig } from '@storybook-vue/nuxt'
import fs from 'node:fs'
import path from 'node:path'

type TranslationValue = string | number | boolean | TranslationStructure | unknown | null
interface TranslationStructure {
  [key: string]: TranslationValue
}

const localesRoot = 'locales'

function mergeAllTranslations(target: TranslationStructure, ...sources: TranslationStructure[]): TranslationStructure {
  return sources.reduce((acc, source) => {
    for (const [key, value] of Object.entries(source)) {
      acc[key] = value
    }
    return acc
  }, target)
}

const getLocales = (): string[] => {
  const localesDir = path.join(__dirname, '../', localesRoot)
  try {
    return fs.readdirSync(localesDir)
      .filter(file => file.endsWith('.json'))
      .map(file => path.basename(file, '.json'))
  }
  catch (error) {
    console.error('Error reading locales directory:', error)
    return []
  }
}

const mergeTranslations = () => {
  const localesDir = path.join(__dirname, '../', localesRoot)
  const outputDir = path.join(__dirname, '../storybook_locales/_locales/general')
  const locales = getLocales()

  const collectAllTranslations = (dir: string, lang: string): TranslationStructure => {
    let translations: TranslationStructure = {}
    const entries = fs.readdirSync(dir, { withFileTypes: true })

    for (const entry of entries) {
      const entryPath = path.join(dir, entry.name)
      if (entry.isDirectory()) {
        translations = mergeAllTranslations(translations, collectAllTranslations(entryPath, lang))
      }
      else if (entry.isFile() && entry.name === `${lang}.json`) {
        const content = JSON.parse(fs.readFileSync(entryPath, 'utf-8'))
        translations = mergeAllTranslations(translations, content)
      }
    }
    return translations
  }

  locales.forEach((lang) => {
    // 1. Π‘ΠΎΠ±ΠΈΡ€Π°Π΅ΠΌ всС страничныС ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Ρ‹
    let merged = collectAllTranslations(path.join(localesDir, 'pages'), lang)

    // 2. ДобавляСм ΠΎΠ±Ρ‰ΠΈΠ΅ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Ρ‹ с ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚ΠΎΠΌ
    const generalFilePath = path.join(localesDir, `${lang}.json`)
    if (fs.existsSync(generalFilePath)) {
      const generalContent = JSON.parse(fs.readFileSync(generalFilePath, 'utf-8'))
      merged = mergeAllTranslations(merged, generalContent)
    }

    // 3. БохраняСм Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
    const outputPath = path.join(outputDir, `${lang}/data.json`)
    fs.mkdirSync(path.dirname(outputPath), { recursive: true })
    fs.writeFileSync(outputPath, JSON.stringify(merged, null, 2))
  })
}

mergeTranslations()

const config: StorybookConfig = {
  stories: [
    '../components/**/*.mdx',
    '../components/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],
  staticDirs: ['../storybook_locales'],
  addons: [
    '@storybook/addon-essentials',
    '@chromatic-com/storybook',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook-vue/nuxt',
    options: {},
  }
}
export default config

πŸš€ Example Projects ​

For complete examples, check out these projects:

  1. Nuxt + Storybook + i18n Example on GitHub
    This repository demonstrates how to integrate Nuxt, Storybook, and nuxt-i18n. It includes setup instructions and configuration details.

  2. Storybook for Nuxt Documentation
    The official documentation for setting up Storybook with Nuxt. It provides detailed steps and best practices.

  3. Interactive Example on StackBlitz
    Explore a live example of Nuxt, Storybook, and nuxt-i18n integration directly in your browser.

Released under the MIT License.