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


오늘은 다국어 지원 가능한 기능 구현 과정을 적어보려고 한다.
원래는 댓글기능보다 먼저 구현하려고 했는데 ,, 갑자기 댓글기능 알아보다가 재밌어서 먼저 끝내버렸다,,,🙄
시작하기 전에 정말 어려웠다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 어려움이 많아서 기능 적용하는 데 시간이 꽤 걸렸다 ,,,
다국어 지원 기능
- 나는 영어랑 일본어를 추가해줬다 ! (해외취업도 관심 있기 때문에 나중에 이력서 낼 때 도움이 되지 않을까? ..🤭)
다국어 구현 방법
구현 방법에는 크게 두가지가 있다.
직접 구현
장점 프로젝트 특성에 맞게 커스터마이징 가능
단점: 기능 구현에 시간과 노력이 많이 필요라이브러리 사용
장점: 빠르고 안정적인 구현 가능
단점: 라이브러리 학습 필요, 프로젝트에 맞게 설정해야 함
나는 라이브러리를 사용했다. 아무래도 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를 보니 꽤 뿌듯했다.
그리고 무엇보다 재밌었다!!!
단순히 기능을 구현하는 데 그치지 않고 실제 사용자 입장에서 언어에 따라 어떻게 경험이 달라지는지 고민하는 과정 자체가 즐거웠다. 앞으로 모든 프로젝트에서도 다국어 기능은 꼭 넣어보고 싶을 정도로 매력적인 작업이었다.(처음엔 진짜 좀 힘듬)..
Subscribe to my newsletter
Read articles from 송수빈 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
