[Web] Web - i18n 이란? - next-i18next
i18n (Internationalization)
i18n
줄임말
Internationalization
의 축약형
i와 n 사이에 18글자가 있기 때문.
a11y(accessiblity)와 l10n(localization), t9n(translation) 등등 모두 같은 원리
뜻
SW 국제화
→ 국제적으로 통용되는 SW를 설계하고 개발하는 것.
국제적으로 서비스하려고 하는 앱은 필수라고 할 수 있다.
i18n vs l10n 비교
그렇다면 i18n과 l10n의 차이점은 무엇일까?
i18n
서비스를 국제화 시키는 것.
고려되어야 하는 사항
- 언어 번역: 모든 사용자가 알아들을 수 있는 언어를 다양하게 지원하여 국제화.
- 유니코드: 문자들을 컴퓨터에서 일관되게 표현하며 다룰 수 있도록 설계된 산업 표준 ⇒ 유니코드를 이용해서 모든 언어를 나타낼 수 있기 때문!
- 리소스 외부화 및 관리: 이미지, 소스코드 등으로부터 리소스 분리 ⇒ 프로그램 수정 없이 다국어 지원 가능하도록!
- 로케일 대응: 날짜, 시간 형식, 달력, 통화, 문자열 정렬 순서 등 다수 로케일에 유연하게 대응 ⇒ 마찬가지로 나라마다 다른 형식들을 하드코딩이 아닌 자동으로 적용되도록 고려!
- Localizability(Internationalization and localization): 지역화 가능성 (서비스의 재설계나 개발없이도 다양한 언어에 적용될 수 있는 정도) ⇒ 번역될 경우 UI에 미치는 영향 분석 ex) 텍스트 확장/축소, 아랍어/중국어 등과 같은 비라틴어 지원, 텍스트 방향 등
l10n
서비스를 현지화 시키는 것.
⇒ 서비스의 타겟 시장의 문화 및 요구 사항에 맞게 서비스를 바꾸는 작업
(서비스가 완성이 된 이후 작업할 수도, 서비스를 만들어 나가는 과정에서 agile하게 작업할 수도 있다.)
이 현지화(l10n)을 할 수 있도록 기반이 되어 있어야 하는 작업이 국제화(i18n)
고려되어야 하는 사항
- 언어 번역: 현지화 언어 번역
-
문화적, 사회적 처리: 언어 뿐만 아니라 그 나라에 맞게 대응
-
ex) 총 쏘는 게임
이미지 출처: [https://miaow-miaow.tistory.com/32]
-
i18n 실제 사용
i18next
는 거의 모든 라이브러리/프레임워크를 지원하고 있음.
react에는 react-i18next
next에는 next-i18next
를 사용함.
i18next config setting
-
패키지 install
next에서는 세 패키지 모두 설치해야 한다.
npm i i18next react-i18next next-i18next
-
next-i18next config 설정
// next-i18next.config.js module.exports = { i18n: { locales: ["en", "ko"], defaultLocale: "ko", localeDetection: true, }, localePath: typeof window === "undefined" ? require("path").resolve("./public/locales") : "/locales", };
- localePath를 설정해주는 이유 vercel 등 클라우드 플랫폼에 배포하는 경우, SSR 코드의 경우 window 객체가 없을 수 있다. 따라서 위와 같이 정의해준다고 한다.
-
next config 수정
// next.config.js const { i18n } = require("./next-i18next.config.js"); /** @type {import('next').NextConfig} */ const nextConfig = { i18n, reactStrictMode: true, }; module.exports = nextConfig;
-
언어 팩 json 파일 생성
기본적으로 다음과 같은 폴더 구조를 가지고 있어야 한다.
common.json 파일이 없는 경우 error가 발생한다.
⇒ 빈 파일만 있어도 상관없다. 무조건 생성 필수!
. └── public └── locales ├── en | └── common.json └── ko └── common.json
common안에 모든 key들을 넣어서 사용할 수 있겠지만,
단어 마다 key를 같은 레벨에 나열하면 너무 알아보기 힘들 것이고,
그렇다고 nesting을 하자니 chaining이 길어지면 사용할 때 헷갈릴 것이다.
(아무래도 사용할 때는 string이기 때문에 chaining 하더라도 자동완성이 안되니 휴먼에러 발생 확률 높음)
ex)
// public/locales/en/common.json { "h1": "title", "description": "This app is for all over the world", "result": { "success": "success!", "failure": "failure!", "warning": "warning!", }, ... // 밑으로 만 개... 😫 } // 컴포넌트 단에서 사용 시 console.log(t("result.success")) // 휴먼 에러 발생 가능성! 😫
그래서 많은 사람들이 따로 파일을 만들어 사용한다.
파일을 나누는 기준은 취향따라 하면 될 것 같다.
일단은 도메인/페이지 별로 나누기로 한다.
// public/locales/en/home.json { "country": "US", "welcome": "Hello, Welcome to America" }
// public/locales/ko/home.json { "country": "한국", "welcome": "안녕하세요. 한국에 오신 걸 환영합니다." }
번역된 언어들의 사용
- 먼저 app에 HOC를 감싸야 한다.
import { appWithTranslation } from "next-i18next"; const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />; export default appWithTranslation(MyApp);
- page에 serverSideTranslations 추가
page-level에서 사용하는 비동기 함수이다. (getStaticProps/getServerSideProps 사용)
// src/pages/index.tsx import { serverSideTranslations } from "next-i18next/serverSideTranslations"; export const getStaticProps = async ({ locale }: GetStaticPropsContext) => { return { props: { ...(await serverSideTranslations(locale || "ko", ["common", "home"])), }, }; };
GetStaticPropsContext에서 locale을 참조할 수 있다. *nodeJS 코드를 포함하고 있기 때문에
next-i18next/serverSideTranslations
모듈에서 가져와야 한다. -
useTranslation(hook): hook으로 사용
import { GetStaticPropsContext, InferGetStaticPropsType } from "next"; import Head from "next/head"; import { useTranslation } from "next-i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; export default function Home( _props: InferGetStaticPropsType<typeof getStaticProps> ) { const { t } = useTranslation("home"); return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> **<div>{t("country")}</div> <div>{t("welcome")}</div>** </main> </> ); }
locale이 “ko” 일 때
locale이 “en” 일 때
하드 코딩하지 않아도 locale이 달라지는 경우 자동으로 다르게 나타나게 된다!! 🎉
추가 기능
-
changeLanguage로 언어 변경 빠르게 가능
import { GetStaticPropsContext, InferGetStaticPropsType } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; export default function Home( _props: InferGetStaticPropsType<typeof getStaticProps> ) { const router = useRouter(); const { t } = useTranslation("home"); const handleLangToggle = (newLocale: string) => { const { pathname, asPath, query } = router; router.push({ pathname, query }, asPath, { locale: newLocale }); }; const changeTo = router.locale === "ko" ? "en" : "ko"; return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <button onClick={() => handleLangToggle(changeTo)}> change lang </button> <div>{t("country", { changeTo })}</div> <div>{t("welcome", { changeTo })}</div> </main> </> ); } export const getStaticProps = async ({ locale }: GetStaticPropsContext) => { return { props: { ...(await serverSideTranslations(locale || "ko", ["common", "home"])), }, }; };
결과
그래서 번역은 누가 업데이트?
시험으로 해본 앱은 번역할 것이 많지 않으니 개발자가 직접 public에 담아서 가져다 쓰면 된다.
그런데 실제 서비스를 하고 있는 웹 앱이라면?
여러 문제가 발생한다.
- 번역할 단어가 너무 많음: NHN의 Dooray는 번역에 사용하는 key값들만 만 개가 넘는다고 한다.
- 코드에서 관리하면 관리가 어려워짐. 보기 힘들고, 용량도 커짐.
- 번역가가 작업을 하는 것이 아닌 개발자가 번역가나 다른 사람에게 파일을 받아서 작업해야 함.
- 코드에 포함되어 있기 때문에 업데이트도 자주 할 수 없음.
⇒ 그래서 개발자는 자동화를 해야 한다.
글로벌 서비스를 만드려면 아무래도 영어 이외에 중국어, 일본어, 등등 지원하면 더욱 더 서비스가 빛을 발할 것이다.
csv 관리 방법
번역 파일을 csv 파일로 만들어서 따로 관리.
csv 파일은 git 관리도 가능하고 보기 편함.
→ json으로 바로 확인이 불가능.
nhn에서 투고한 글에서의 방법
- 구글 스프레드 시트
기본적인 플로우
- 구글 스프레드 시트의 엑셀에 단어들을 번역가들이 작업.
- 엑셀의 값들을 모두 불러옴.
- object / json으로 컨버팅하여 사용.
사용하는 패키지
npm install i18next
npm install -D i18next-scanner
npm install -D google-spreadsheet
i18next 단어를 전달하여 번역한 (매핑된) 단어를 반환해주는 자바스크립트 국제화 라이브러리 (복수형, 인터폴레이션, 문맥 기능도 있음)
i18next-scanner 18next.t() 나 i18n.t() 같은 함수를 스캔하여 key 추출, 언어별 json 파일 생성 ⇒ script로 실행 필요 (codegen 처럼)
google spread sheet는 구글 스프레드 시트 api를 js에서 사용할 수 있도록 래핑한 라이브러리. 시트 조작 가능.
⇒ upload와 download util을 만들어서 json으로 생성된 번역 단어들을 구글 스프레드 시트에 맞게 열을 추가한다.
시도는 해보지 않았지만 해보고 싶은 방법
- firebase remote config 혹은 db
기본적인 플로우
- 번역가는 firebase에서 데이터 바꾸고
- firebase에서 데이터 가져와서 사용
- 혹은 key value json으로 저장한다고 하면 따로 버전이나 정기 배포 없이 바뀐 정보를 보여줄 수 있으니 좋지 않을까
- firebase dependency를 추가해두면 나중에 A/B 테스트 도입도 고려할 수 있다!
⇒ firebase remote config가 데이터를 얼마나 저장할 수 있을지, 번역가가 작업하기는 편한지 등 확인 필요
참고 아티클
https://all-dev-kang.tistory.com/entry/리액트-글로벌한-웹을-향하여-react-i18n-다국어지원#들어가며
https://meetup.nhncloud.com/posts/295
https://github.com/i18next/next-i18next
https://www.i18next.com/overview/supported-frameworks
https://miaow-miaow.tistory.com/32
https://tolgee.io/blog/localization-basics-S01E01
https://crowdin.com/blog/2022/07/14/internationalization-vs-localization
댓글남기기