[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

송수빈송수빈
4 min read

오늘은 다국어 지원 가능한 기능 구현 과정을 적어보려고 한다.

원래는 댓글기능보다 먼저 구현하려고 했는데 ,, 갑자기 댓글기능 알아보다가 재밌어서 먼저 끝내버렸다,,,🙄

시작하기 전에 정말 어려웠다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 어려움이 많아서 기능 적용하는 데 시간이 꽤 걸렸다 ,,,

다국어 지원 기능

  • 나는 영어랑 일본어를 추가해줬다 ! (해외취업도 관심 있기 때문에 나중에 이력서 낼 때 도움이 되지 않을까? ..🤭)

다국어 구현 방법

구현 방법에는 크게 두가지가 있다.

  • 직접 구현

    장점 프로젝트 특성에 맞게 커스터마이징 가능
    단점: 기능 구현에 시간과 노력이 많이 필요

  • 라이브러리 사용

    장점: 빠르고 안정적인 구현 가능
    단점: 라이브러리 학습 필요, 프로젝트에 맞게 설정해야 함

나는 라이브러리를 사용했다. 아무래도 next 프레임워크 사용은 처음이기도 하고 아무래도 다국어 지원을 위해서는 직접 구현보다는 안정적이고 널리 쓰이는 라이브러리인 next-i18next가 좋다고 판단했기 때문이다.

추가로 댓글 기능을 Giscus를 사용해서 구현했기 때문에 찰떡궁합쓰-!

  • Giscus는 GitHub Discussions를 댓글 시스템으로 쉽게 붙일 수 있게 해주는 서비스

  • next-i18next는 Next.js에서 다국어 지원을 편하게 해주는 라이브러리


구현 과정

//설치
npm install next-i18next react-i18next i18next

next-i18next.config.js 파일 생성

const nextI18NextConfig = {
  i18n: {
    defaultLocale: "ko",
    locales: ["ko", "en", "ja"],
    localeDetection: true,
  },
  ns: ["common"],
  defaultNS: "common",
  react: {
    useSuspense: false,
  },
};

export default nextI18NextConfig;

next.config.mjs 설정

const nextI18NextConfig = {
  i18n: {
    locales: ["ko", "en", "ja"],
    defaultLocale: "ko",
  },
};

export default nextI18NextConfig;

Comments

import Giscus from "@giscus/react";
import { useTranslation } from "next-i18next";

export default function Comments() {
  const { i18n } = useTranslation();
  const lang = ["ko", "en", "ja"].includes(i18n.language)
    ? i18n.language
    : "en";

  return (
    <Giscus
      lang={lang}
    />
  );
}

fallback인 경우에는 영어로 처리하게 해줬다!

app.js

import { appWithTranslation } from "next-i18next";
import nextI18NextConfig from "../../next-i18next.config";
function App() {
  return (
...
  );
}
export default appWithTranslation(App, nextI18NextConfig);

appWithTranslation

  • next-i18next에서 제공하는 HOC(Higher Order Component) 이걸 감싸야 Next.js 앱 전체에서 다국어 컨텍스트와 기능을 사용할 수 있으며, 모든 페이지와 컴포넌트에서 useTranslation 훅 쓸 수 있게 해줬다.

index.js

import Comments from "@/components/Comments";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import nextI18NextConfig from "../../next-i18next.config";

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common"], nextI18NextConfig)),
    },
  };
}
  • getStaticProps 에서 serverSideTranslations 호출

    • 현재 요청된 locale에 맞춰 필요한 번역 리소스를 빌드 타임에 미리 불러와 페이지에 주입해줬다. 이게 있어야지SSR시점에 번역 텍스트가 반영된다.
  • Comments 컴포넌트 렌더링:

    • 앞서 말한 Giscus 댓글 컴포넌트를 다국어 설정과 함께 사용해줬다.

겪었던 트러블 슈팅 📄

여기까지 보면 세팅 하는건데 뭐가 어려웠다는거야?! 라는 생각을 할 수 있지만,, gif에서 보면 언어를 선택해도 이름이 바뀌지 않는 문제를 오랜시간동안 해결하지 못 했다…

콘솔로 찍어봤을때는 ja, en 이렇게 잘 찍히는데 정작 반영이 안된것,,,

왜 이런 문제가 생겼는지 알아봤는데

Next.js에서는 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 HTML이 다르면 경고가 뜨는데 그걸 Hydration Mismatch라고 한다.

쉽게 말해서

  • 서버에서는 i18n.language'en'이라고 판단해서 <span>English</span> 렌더링

  • 근데 클라이언트에서는 브라우저 언어에 따라 'ja'로 바뀌어서 <span>日本語</span>로 바뀜
    → React가 "어? 서버랑 클라이언트에서 HTML 다르네!" 라고 경고를 띄움

문제이다.

해결 방법

//Header.jsx
{!isMobile && <span suppressHydrationWarning>{t("name")}</span>}

이를 방지하기 위해 <span suppressHydrationWarning> 속성을 사용하여 해당 경고를 억제했다.
(클라이언트 사이드에서 정확한 언어가 적용되는 상황에서 유효한 방법!)

잘 반영된다,, 🤭

조회수도 반영해보자 -!

public/locales/en,ko,ja 각 언어에 해당하는 언어를 여기에 넣어주면 된다.

이런식으로

//TotalView.js
import { useEffect, useState } from "react";
import { getTotalViews } from "../../lib/incrementView";

export default function TotalView() {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    getTotalViews()
      .then(setTotal)
      .catch(() => setTotal(0));
  }, []);

  return (
    <div className="text-right sm:text-sm text-xs text-gray-600 dark:text-gray-400 px-4 ml-auto">
      조회수 | <span className="font-semibold">{total.toLocaleString()}회</span>
    </div>
  );
}

지금은 이렇게 하드코딩으로 되어 있어서 당연히 언어를 선택해도 계속 조회수 | 숫자 회 이렇게 뜨는 것!

수정해보자

//TotalView.js
import { useTranslation } from "next-i18next";

export default function TotalView() {
  const { t } = useTranslation("common");

  return (
    <div className="text-right sm:text-sm text-xs text-gray-600 dark:text-gray-400 px-4 ml-auto">
      {t("views")} |{" "}
      <span className="font-semibold">
        {total.toLocaleString()}
        {t("views_suffix")}
      </span>
    </div>
  );
}

잘 반영된다 😊

🌱 느낀점

Next.js로 블로그를 만들면서 처음으로 다국어 지원을 구현해봤는데 생각보다 고려해야 할 부분이 많아서 꽤 흥미로운 경험이었다.
특히 next-i18next를 사용하면서 SSR 클라이언트 hydration 문제 Giscus처럼 외부 라이브러리와의 언어 연동 등 다양한 상황을 마주했다.

처음에는 단순히 JSON에 번역만 넣으면 끝날 줄 알았지만 실제로는 페이지 렌더링 시점, 클라이언트와 서버의 불일치, suppressHydrationWarning, ready 상태 체크 같은 세세한 부분까지 신경 써야 했다…(솔직히 어려웠음😬)

조회수 표시 같은 UI 요소도 하드코딩된 텍스트를 하나씩 번역 키로 바꾸는 과정이 신경써야 할 부분이 꽤 많지만 결과적으로 완성된 다국어 UI를 보니 꽤 뿌듯했다.

그리고 무엇보다 재밌었다!!!
단순히 기능을 구현하는 데 그치지 않고 실제 사용자 입장에서 언어에 따라 어떻게 경험이 달라지는지 고민하는 과정 자체가 즐거웠다. 앞으로 모든 프로젝트에서도 다국어 기능은 꼭 넣어보고 싶을 정도로 매력적인 작업이었다.(처음엔 진짜 좀 힘듬)..

0
Subscribe to my newsletter

Read articles from 송수빈 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

송수빈
송수빈