[Next.js] 프로젝트에 다국어 지원하기!

9rganizedChaos·2024년 10월 10일
3
post-custom-banner

개발한 프로젝트 Dio는 댄스 스튜디오 관리 웹 서비스인데, 서울 소재의 댄스 스튜디오에는 외국인 수강생들이 정말 많다. 그래서 웹사이트에 다국어 대응을 하기로 했다. 나의 경험에 비추어볼때, 어설프더라도 일단 유저의 모국어로 지원을 해주면 접근성이 한결 높아지니까.

Middleware를 통해서 구현하면 된다고, 애초에 공식문서에 작성되어 있어서 별다른 고민 없이 구현을 시작할 수 있었다. (공식문서를 신뢰하는 편...)

문서를 따라서 구현하다보면, 딱히 구현자체가 어렵지는 않았고,
개발하며 고민되었던 포인트? 이제 삽질이 몇 가지 있어서, 경력기술서 작성 겸 포스팅을 적어본다.

다국어 대응을 구현하며, 고민되었던 지점!

1) 서버에서 내려오는 에러 메시지는 클라이언트에서 다국어 대응을 해줘야 할까, 서버에서 다국어 대응을 해줘야 할까?!

결론부터 작성하자면, 나는 서버에서 다국어 대응을 해서 에러 메시지를 반환해주는 방식을 택했다.
일단 서버에서 다국어 대응하는 방식과 클라이언트에서 다국어 대응하는 방식에 대해 좀 더 얘기해보고 싶다.

클라이언트에서 에러 메시지 다국어 대응하기

  • 클라이언트에서 에러메시지를 대응했을 때 좋은 점은, 다국어 대응에 있어 관리 포인트가 줄어든다는 점이라 생각했다. ("모든 언어관련 설정은 클라이언트에서 전담한다!!" 와 같은 접근 방식)
  • 서버에서 에러를 반환할 때, 에러 코드(http 상태 코드 말고 NOT_ALLOWED <<< 이런 방식의 에러 메시지)를 함께 보내주면 클라이언트에서는 해당 에러코드를 현재 언어 설정에 맞게 노출해주는 방식이다.

서버에서 에러 메시지 다국어 대응하기

서버에서 대응하는 경우는, 클라이언트에서 API 요청을 쏠 때, 헤더에 Accept-Language 정보를 담아서 보내고 서버는 해당 정보를 보고 알맞은 언어로 응답을 내려주는 방식을 생각했다.

내 선택

결론적으로 내가 서버에서 에러 메시지 다국어 대응을 하기로 선택한 것은 리소스가 부족했기 때문이었다. 풀스택 개발자로 혼자 작업하고 있기 때문에 에러 코드를 일일이 정리할 시간이 없었다. 별일 아닌 것 같아도 매번 모든 에러에 대해 에러코드 네이밍을 하는 것도 꽤 골치 아픈 작업일 거고. 특히 서버에서 다국어를 대응을 해서 에러를 반환해주면, 특별한 케이스가 아닌 경우, 메시지를 그대로 노출해주면 되어서 불필요한 클라이언트 코드가 줄어들게 된다.

또 하나, 이메일 발송이나 알림톡 발송과 같은 API 요청을 보낼 때 어차피 Accept-Language 정보를 담아서 서버에 요청을 보내야 하고, Server도 결국 다국어 관련 리소스를 관리해야 했다. 그러한 이유로 에러 메시지에 대해서는 dictionary JSON 파일에서 관리하지 않고, 서버에서 다국어 대응된 에러메시지를 내려주는 방식으로 코드를 작성하였다.

2) 약관 및 방침 문서 페이지와 같이 텍스트가 가득한 페이지는 dictionary 대신 언어별 컴포넌트를 만들자.

처음에는 위와 같은 페이지들도 전부 dictionary에서 관리했었다.

이런 방식으로 작성했는데, 문제는 수정할 때 가독성이 너무 떨어진다는 단점이 있었다.
그래서 텍스트가 가득한 페이지는 유지보수를 쉽게 하기 위해 그냥 따로 각각의 컴포넌트를 만들어주었다.

이 경우가 가독성이 압도적으로 낫다.

import TermsOfService20241001English from './EnglishContent';
import TermsOfService20241001Korean from './KoreanContent';

const TermsOfServicePage20241001Page = async ({
  params: { lang },
}: Readonly<{
  params: {
    lang: 'ko' | 'en';
  };
}>) => {
  return lang === 'ko' ? (
    <TermsOfService20241001Korean />
  ) : (
    <TermsOfService20241001English />
  );
};

export default TermsOfServicePage20241001Page;

그리고 위와 같이 렌더링 해주었다.

3) JSON 작성시 속성명은 Default Language 텍스트 그대로

[AS-IS] 원래는 이렇게 속성명을 추상화해서 작성했었다.

  "footer": {
    "companyName": "Diosmio",
    "address": "606-1063, 43, Daeji-ro, Suji-gu, Yongin-si, Gyeonggi-do, Republic of Korea",
    "propertyCEO": "CEO",
    "propertyBusinessNum": "Business Number",
    "propertyEmail": "Email",
    "propertyPhone": "Phone",
    "PropertyMailOrder": "Mail-order Business Registration Number",
    "valueCEO": "Jungho Choi",
    "linkTermsOfService": "Terms of Service",
    "linkPrivacyPolicy": "Privacy Policy"
  },

그런데 위와 같이 작성하게 되면, 나중에 어떤 텍스트가 어디에 매치되는지 헷갈리는 경우가 은근히 자주 생긴다. 그리고 무엇보다 네이밍의 압박... 텍스트가 한 두개가 아닌데, 거기에 일관성을 부여하고 싶다는 욕심까지 생기니, 쓸데없는 데에 시간을 낭비하게 된다는 것을 깨닫고 아래와 같이 수정했다.

[TO-BE] 그냥 default 텍스트 그대로 속성명으로 사용

 "footer": {
      "디오스미오": "Diosmio",
      "경기도 용인시 수지구 대지로 43, 6층 606-1063호 (죽전동, 금강프라자)": "606-1063, 43, Daeji-ro, Suji-gu, Yongin-si, Gyeonggi-do, Republic of Korea",
      "대표": "CEO",
      "사업자번호": "Business Number",
      "이메일": "Email",
      "전화번호": "Phone",
      "통신판매업 신고 번호": "Mail-order Business Registration Number",
      "최정호": "Jungho Choi",
      "서비스 이용 약관": "Terms of Service",
      "개인정보 처리방침": "Privacy Policy"
    }

적용된 코드를 보아도, 변경된 편이 훨씬 가독성이 좋고 직관적이다. 그리고 추후에 번역을 외주로 맡기게 되더라도 이 경우가 훨씬 훨씬 편할 것이다.

4) 마지막으로 JSON의 구조

처음에는 컴포넌트에서 시작해서 개발하다보니, "footer", "class-card", "dancer-card" 이런식으로 속성을 나열했었는데, 나중되니, 관리가 힘들고, 몇 백줄이 넘는 파일에서 내가 찾는 텍스트의 위치를 정확히 찾기가 어려웠다.

그래서 결국 프로젝트의 pages 폴더 내의 구조를 그대로 JSON 파일에도 적용했다!
아래 사진을 보면 더 이해가 쉬울 것 같다.

폴더 구조

dictionary JSON 파일 구조

그럼 이만 포스팅 마무리!

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!
post-custom-banner

0개의 댓글