본문 바로가기
Frontend(Web)/개발 일지

다국어 서비스 적용 회고

by MINTOEngineer 2024. 1. 16.
이 글은 서비스에 다국어를 적용하는 과정에서 들었던 고민을 정리한 글입니다.
제가 정답은 아니지만 다국어를 처음 적용하려고 하거나 적용중인분한테 도움이 될꺼라고 생각합니다. 

 

프로젝트 시작전 고민

다국어 서비스를 구현하기 위해서는 두가지 방법에 대한 고민이 있었습니다.

 

첫 번째는 라이브러리 없이 사용하는 경우입니다.

라이브러리 없이 전역 상태에 locale값을 선언해 이 상태값을 기준으로 조건문으로 영어와 한국어를 넣는 방법에 대해서 처음에 고민을 해봤습니다. 

하지만 이 방법은 컴포넌트에 JSX가 길어져 가독성에 좋지 않겠다는 생각이 들었습니다. 그리고 영어 한국어 이외에 추가적인 언어를 지원하게 되는 경우 조건문이 복잡해지고 코드가 더러워져 가독성에도 좋지 않고 확장성에도 좋지 않다는 생각이 들었습니다.

 

두 번째는 라이브러리의 사용입니다. 

다국어 지원을 위한 라이브러리에는 크게 4가지 종류가 있습니다.

 

1. rosetta

2. globalize

3. Format.js (react-intl)

4. react-i18next, next-i18next

 

첫 번째 rosetta 라이브러리는 298bytes로 작은 용량을 가지고 있는 라이브러리라는 장점이 있습니다. 하지만 마지막 업데이트가 4년전이고 최근에는 npm trends를 봐도 다운로드수가 확연히 줄어 관련된 레퍼런스가 많이 부족해 사용하지 않는게 좋다고 판단했습니다.

 

두 번째는 globalize입니다. 마지막 업데이트가 2년전이고 사용량은 많았지만 최근에 급격히 사용량이 감소하고 있는 라이브러리입니다. rosetta와 마찬가지로 비슷한 레퍼런스가 많이 적기 때문에 사용을 하지 않도록 결정했습니다.

 

세 번째는 Format.js입니다. 단일 json 파일을 기준으로 다국어를 지원하고 기본적으로 세팅하는 보일러 플레이트가 적어 매력적으로 다가왔습니다. 그리고 다운로드수도 많아 참고할 수 있는 레퍼런스가 많은 특징이 있습니다. 

예전에는 문서화가 부실한점이 있었지만 Format.js 사이트를 만들어 문서화를 깔끔하게 정리해 놓았습니다.

 

네 번째는 react-i18next입니다. 다국어 지원을 위한 라이브러리중 가장 많은 사람들이 사용하고 있어서 레퍼런스가 굉장히 많고 글쓰는 시기 기준으로 최근 업데이트도 21일전으로 자주 업데이트가 되고 관리되고 있는 라이브러리입니다.

react-intl과 다르게 여러 json 파일로 다국어를 지원하고 있습니다. 하지만 초기에 세팅해야하는 보일러 플레이트가 react-intl보다는 많은 편입니다. 

최근에는 next-i18next도 지원해 SSG나 SSR의 경우도 대응하는게 가능해졌습니다.

결국 뭐를 선택했는데?

저희 서비스는 Next를 사용하고 있어 SEO에 대응하기 위해 SSG와 SSR 모두 활용하고 있는점과 프로젝트 크기가 커 컴포넌트 종류별로 json을 관리하고 싶고 꾸준히 관리되고 업데이트가 되고 있는 next-i18next를 사용하는게 좋겠다는 생각이 들었습니다. 

 

next-i18next 사용법

사용법은 공식문서나 다른 블로그에 많으니 이 글은 짧게만 해당 내용을 다루겠습니다. 참고로 Next12 버전 기준입니다.

 

next-i18next, react-i18next, i18next 세개를 모두 설치해줘야합니다.

참고로 next-i18next만 설치해주면 로컬에서는 잘 동작하지만 배포시 서버에서 빌드 실패할 수 있기 때문에 3개 모두 설치해줘야합니다.

 

 

public 폴더에 localse 폴더를 만들어 지원하고자 하는 언어의 폴더를 만들고 내부에 컴포넌트나 주제별로 json 파일을 위 사진처럼 만들어줍니다.

 

module.exports = {
  i18n: {
    defaultLocale: 'ko',
    locales: ['ko', 'en'],
  },
};

 

최상위 경로에 next-i18next.config.js 파일을 만들어 위에 코드를 작성해 기본 언어는 어떤거로 할지를 정하고 지원하고자 하는 언어 폴더를 localse 배열에 인자로 넣어줍니다.

 

onst { i18n } = require('./next-i18next.config');

const path = require('path');
const nextConfig = {
  reactStrictMode: true,
  i18n,

 

next.config.js 파일로 가서 앞에서 설정한 config 파일을 넣어줍니다.

 

import { appWithTranslation } from 'next-i18next';
import store from '../store';

function App({ Component, pageProps }) {

  return (
    <>
      <Provider store={store}>
        <Component {...pageProps} />
      </Provider>
    </>
  );
}

export default appWithTranslation(App);

 

_app.js 파일에 가서 App 컴포넌트를 appWithTranslation 함수로 감싸줍니다. 

 

import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ['header'])),
    },
  };
}
export async function getServerSideProps(context) {
  const { input } = context.params;
  ...

  return {
    props: {
      ...(await serverSideTranslations(context.locale, [
        'header',
        'footer',
        'free-search',
      ])),
      initialMarks: marks,
      paramsInput: input,
      em: errormessage,
      forHis: forHis,
    },
  };
}

 

사용하고자 하는 일반 컴포넌트의 경우 해당 컴포넌트를 감싸고 있는 페이지 컴포넌트에 SSG나 SSR로 serverSideTranslations를 호출해서 반환해줘야 합니다. 

참고로 인자에는 미리 만들어둔 json 파일중 사용하고자 하는 번역의 script 파일 이름을 배열에 인자로 넣어줘야합니다.

import { useRouter } from 'next/router';
import { useTranslation } from 'react-i18next';

export default function HomeIntroduceA({ onClickViewMore }) {
  const { t } = useTranslation('markview-main');
  const router = useRouter();
  const { locale } = router;
  ...
  
  return (
  	<p>{t('part1_title3')}</p>
  )

 

그리고 사용하고자 하는 컴포넌트에 useTranslation 이라는 hook을 호출해준다. 그리고 사용하고자 하는 json 파일의 이름을 기입해줍니다.

그리고 JSX 태그에 내용으로 위와 같은 문법으로 작성해주면 언어를 변환하는 과정에서 자동으로 해당 스크립트가 변경이 됩니다. 

 

 

그럼 이게 끝인가?

 

물론 당연히 끝이 아닙니다. ㅎㅎ 

 

직접 다국어를 서비스에 적용하면서 생겼던 고민들

 

이대로 끝이면 좋겠지만 직접 서비스에 적용해보면서 여러 고민들이 있었고 이를 어떻게 문제로 문제로 인식했고 해결했는지를 얘기 해보겠습니다.

 

1. 서버에서 받아오는 데이터는 어떻게 번역할 수 있을까?

 

서비스에 보여지는 부분들은 모두 json으로 스크립트를 작성해 번역할 수 있었습니다. 

하지만 서버에서 받아오는 미지의 값들까지 스크립트에 작성하는건 불가능하다는 생각이 들었습니다. 

 

이를 위해서는 서버분과 얘기를 통해 특정 파라미터 값등을 기준으로 반환 데이터를 영어로 받을지 한국어로 받을지 정해 서버 API를 수정해야 했지만 시간 관계상 불가능했고 단순히 한국기준 검색의 경우 한글로 데이터를 받아오고 유럽과 미국 데이터 검색의 경우는 순전히 영어로만 오기 때문에 단순히 외국에 나가 시연이 목적이라 그대로 서버 API는 수정하지 않는 방향으로 결정했습니다.

 

2. 영어로 언어를 변경하면서 스타일이 깨져요.

기존 한글의 경우보다 영어로 변경하면 글자의 길이가 늘어날 수 있습니다. 

 

국제등록번호 => International registration number

 

단어만 해도 기존 한글보다 많은 길이의 영어 단어로 바뀝니다. 

이를 기반으로 기존 스타일대로 적용하면 스타일이 깨지고 예쁘지 않게 UI가 보이는 문제가 있었습니다.

 

이를 해결하기 위해 처음은 해당 영어 단어를 더 줄일 수 있는지를 고민했습니다. 그래도 불가능하다면 locale값을 참조해 추가적인 class이름을 붙혀 영어일 경우 박스 크기나 font의 크기를 줄이도록 설정 했습니다. 

줄바꿈이 필요한 경우 json 파일에 만들어서 사용하기에는 각 반응형 디자인마다 줄바꿈의 필요 여부가 다르기 때문에 결국 JSX에 조건문을 통해 렌더링 해야할꺼 같에서 json은 만들지 않고 JSX에 조건문을 통해 렌더링 되도록 설정 했습니다. 

 

 

결론은 json 파일로 스타일을 대응하도록 만들기에는 힘듭니다. ㅠㅠ

 

3. 영어로 검색할경우 SEO에 대응하는게 가능할까?

 

      <Head>
		...
        <title>{t('title')}</title>
      </Head>

 

next-i18next의 경우 앞에서 SSG나 SSR에 대응하기 위한 라이브러리라는 특징 때문에 선택했다고 했습니다.

next에서 사용할 경우 Head를 설정하는 태그의 경우 미리 서버에서 해당 title 태그의 값이 영어나 한국어 키워드로 입력이 된 상태로 클라이언트에게 응답을 내려주니 구글 크롤러의 경우 영어나 한국어의 키워드를 미리 인지해서 가져올 수 있어 SEO에도 대응되는게 가능했습니다.