[Next.js 13 + Storybook7] 다국어 기능 Story 적용하기

👉 Next.js 14 + Storybook8도 동일하게 작업

👉 storybook-i18n addon만 3버전으로 설치

  1. 레포지토리 준비

    1. Next.js 13 v13.5.6
    2. Yarn Berry v4.1.1
    3. Storybook7 v7.6.17
  2. [next-intl] next-intl 공식문서에서 Storybook과 함께 사용할 수 있음 확인

    next-intl

    스크린샷 2024-04-14 오후 1.20.41.png

  3. [Storybook Addon] storybook-i18n 설치

    Storybook i18n Addon | Storybook: Frontend workshop for UI development

    스크린샷 2024-04-11 오후 8.21.19.png

  4. 스토리북의 Global Decorators 를 사용하여 next-intl 적용하기

    1. .storybook > preview.ts

      • preview.ts는 각 stories 파일들에 대하여 전역적인 설정을 할 수 있는 영역
        • Next.js 의 가장 root에 위치한 layout.tsx 과 비슷한 역할

        • 모든 생성 stories 파일이 preview.ts의 영향력 안에 있음

          storybook의 preview.ts에서 한 설정의 적용 영역

          storybook의 preview.ts에서 한 설정의 적용 영역

    2. preview 영역에 locale 툴바 적용하기 ( .storybook > preview.ts )

      Toolbars & globals • Storybook docs

      1. 툴바 메뉴 설정하기

        1. globalTypes를 사용해서 툴바 내부에 여러가지 기능 버튼을 적용할 수 있음

          const preview: Preview = {
          	globalTypes: {
              locale: {
                description: '국제화 locale',
                defaultValue: 'ko',
                toolbar: {
                  icon: 'globe',
                  items: [
                    { value: 'ko', right: '🇰🇷', title: '한국어' },
                    { value: 'en', right: '🇺🇸', title: 'English' },
                    { value: 'ja', right: '🇯🇵', title: '日本語' },
                  ],
                },
              },
            },
          }
          

          preview 화면에 locale 버튼이 툴바에 추가된 모습

          preview 화면에 locale 버튼이 툴바에 추가된 모습

      2. 생성된 모든 stories 파일에서 next-intl을 사용할 수 있도록 NextIntlClientProvider를 적용

        1. stories 파일은 프로젝트의 기존 설정이 적용되는 것이 아닌 storybook 별도의 환경에서 작동

        2. preview.ts에서 NextIntlClientProvider 설정을 전역적으로 해줘야함

        3. Storybook의 Decorator 설정을 이용

          Decorators • Storybook docs

          [Storybook][MUI] 스토리북 실무에서 사용하기

          • Story 상위에 추가적인 마크업을 생성하여 래핑하는 기능

          • 각 스토리에서 개별로 decorator를 적용할 수도 있고, preview.ts 에서 작성하면 전역적으로 적용됨 (Global Decorator)

            export const Primary: Story = {
              **decorators: [**
                (Story) => (
                  <div className="i_am_decorator">
                    <Story />
                  </div>
                ),
              **],**
              args: {
                primary: true,
                label: 'Button',
              },
            }
            

            기존 스토리 컴포넌트 바로 위에 추가로 생성된 decorator 마크업

            기존 스토리 컴포넌트 바로 위에 추가로 생성된 decorator 마크업

        4. preview.ts에서 decorators 설정을 적용 (Global Decorators)

          1. decorators는 배열을 받기때문에 decorator가 많아질 경우, 별도 파일로 빼서 관리할 수도 있음

          2. decorators에서는 globalTypes에 정의된 locale의 value를 가져와서 활용할 수 있음

            • context 매개변수를 사용해서 데이터를 decorators에 가져옴
            • context.globals.locale
            • globalTypes의 value는 변경될때마다 UI를 렌더링함
            • 툴바에 있는 locale 메뉴 값을 변경할때마다 스토리 내부 언어가 변경됨
            import { NextIntlClientProvider } from 'next-intl'
            import * as KoMsg from '../messages/ko.json'
            import * as EnMsg from '../messages/en.json'
            import * as JaMsg from '../messages/ja.json'
            
            const preview: Preview = {
            	**globalTypes: {**
                **locale**: {
                  description: '국제화 locale',
                  defaultValue: 'ko',
                  toolbar: {
                    icon: 'globe',
                    items: [
                      { value: 'ko', right: '🇰🇷', title: '한국어' },
                      { value: 'en', right: '🇺🇸', title: 'English' },
                      { value: 'ja', right: '🇯🇵', title: '日本語' },
                    ],
                  },
                **},**
              },
              
              decorators: [
                (Story, **context**) => {
                  const **selectedLocale** = context.globals.locale
            
            			// NextIntlClientProvider에 필요한 messages를 
            			// 변경되는 globalTypes locale 값에 맞게 언어 json을 바꿔줌
                  const convertLocaleMsg = () => {
                    switch (selectedLocale) {
                      case 'ko':
                        return KoMsg
                      case 'en':
                        return EnMsg
                      case 'ja':
                        return JaMsg
                    }
                  }
            
                  return (
                    <NextIntlClientProvider locale={selectedLocale} messages={convertLocaleMsg()}>
                      <Story />
                    </NextIntlClientProvider>
                  )
                },
              ],
            }
            
        5. 적용한 전역 설정을 받을 Button.stories.tsx 를 생성

          • components > Buttons.tsx

            • Button.tsx 일반 컴포넌트를 생성함
          • stories > Button.stories.tsx

            • 생성한 Button.tsx 컴포넌트를 stories파일에 import함

            • Button.stories.tsx

              • Button.tsx 이라는 1개의 컴포넌트가 가질 수 있는 여러 케이스를 시각화한것
              • 카탈로그처럼 설명과 함께 모든 케이스의 UI 화면을 전시한 것
            • stories 파일에서 함수를 export 시키면 preview화면에 등록됨

            • 다른 화면에서 하듯이 useTranslations 훅을 이용하여 언어 적용

            • Button 컴포넌트의 텍스트로 들어가는 props → label

              import { Button } from './Button' // 일반 Button.tsx 컴포넌트
              import { useTranslations } from 'next-intl'
              
              ...
              
              Button.stories.tsx에 대한 여러가지 설정
              
              ...
              
              // 1개의 story export
              // Button.stories.tsx 는 여러개의 story를 export하는 파일
              export const StoryWithLocale = {
                render: () => {
                  const t = useTranslations('About')
              
                  return <Button label={t('title')} primary={true} />
                },
              }
              

              Preview 화면에 등록된 StoryWithLocale 함수

              Preview 화면에 등록된 StoryWithLocale 함수

              툴바 Locale 메뉴 변경값에따라 바뀌는 Story 화면

              툴바 Locale 메뉴 변경값에따라 바뀌는 Story 화면