I HATE FLYING BUGS logoFE Chapter

다국어 지원 가이드

https://lingui.dev/ 라이브러리 매크로 방식으로 주로 사용

https://lingui.dev/ 라이브러리 매크로 방식으로 주로 사용

작업 과정

  1. 메시지를 Trans 매크로로 감싸거나, t(i18n) 함수로 감쌉니다.
  2. 메시지 번역 목록(catalog)를 생성하기 위해 extract 명령을 실행합니다.
  3. 메시지 번역 목록(catalog)를 번역합니다 (일반적으로 번역자에게 전달).
  4. 런타임 번역 목록(catalog)를 생성하기 위해 compile을 실행합니다.
  5. 런타임 번역 목록(catalog)를 로드합니다.
  6. Trans나 t 함수에서 번역 목록(catalog)를 사용합니다.

번역 작업 가이드

아래 커밋 참고 https://github.com/mildang/mildang-frontend/commit/375c99570f0f5969bdf6cf69608cf33e792667df#diff-de089eb0bae57e021a6785e5e4953f562b0c794b14b125e4d8e9e18cd5ed4110R35

https://github.com/mildang/mildang-frontend/commit/375c99570f0f5969bdf6cf69608cf33e792667df#diff-bbe486217b49b36062086e2b5ccd5e900a648a85ada189e0a2477bb92ff8e8a7

리엑트 컨텍스트에서 사용하기 (권장 되는 방식)

왠만하면 텍스트 안에 넣기

import { Trans } from '@lingui/macro';
<Text variant="caption1-M" color="mui.text.tertiary">
  <Trans>상태</Trans>
</Text>;

밖에서 감싸는 경우

<Trans>
  <Text color="success.10" variant="body4-R" as="span">
    {row.original.completeStudentCount}{' '}
  </Text>
  <Text color="common.10" variant="body4-R" as="span">
    / {row.original.totalStudentCount} 명
  </Text>
</Trans>

이런 경우는 번역에서 전체 모양을 알아야 할 때입니다. 3 / 4 명 이런 문맥이 있어야 제대로 번역이 되는 경우는 밖에다 감싸서

일반 ts 파일에서 사용하기 (string 타입을 활용해야 할때)

i18n 컨텍스트가 필요해서 정적이였던 라벨 변수를 함수로 변경해서 i18를 넘겨받아야 합니다.

toast 같은 경우 string 타입을 사용해야 하기 때문에 react Trans 컴포넌트는 활용 못하고 t(i18n)을 활용해야 하며 i18n를 리엑트 훅에서 가져와서 넣어야 언어 인식이 올바르게 됩니다.

import { t } from '@lingui/macro';
import { useLingui } from '@lingui/react';

// react context에서 i18n 가져오기
const { i18n } = useLingui();

toast.success(t(i18n)`${assignmentTitle} 삭제되었습니다.`);

context 추가하기

  • context는 번역가가 텍스트의 의도와 사용 위치를 이해하도록 돕는 부가적인 설명입니다.
  • comment는 번역가에게 추가 정보를 제공하는 필드입니다.
  • 모든 설명은 context로 작성하여 일관성을 유지합니다.

참고

Trans 이용

UI 깨지는 경우

<Trans context="버튼 안의 텍스트 들이기 때문에 짧고 간결하게 써주세요.">이어서 학습하기</Trans>

부가 설명

<Chip
  size="small"
  label={<Trans context="학생이 이 문제를 풀지 않고 넘어갔다는 것을 의미합니다.">건너뛴 문항</Trans>}
/>

일반 ts 파일

t(i18n)({
  context: '왼쪽, 오른쪽의 "선택지 = 카드"를 서로 이어주지 않으면 보이는 문구',
  message: `연결하지 않은 카드가 있습니다. 모든 카드를 바르게 연결해주세요.`,
});

DS에서

• DS 내부에서는 기술적인 이유로 macro를 사용할 수 없습니다.(id 자동 부여 x) • 이에 따라 context 대신 comment로 작업하고 있습니다. • 추후 상황이 변경되면 context를 활용하도록 업데이트될 수 있습니다.

{
  i18n.t({
    message: '그리드',
    id: '그리드',
    comment:
      '그리드는 가로세로로 이루어진 격자선을 의미합니다. 그리드를 켜면 배경에 그리드가 펼쳐 보여집니다',
  });
}

\n 개행 처리

Bad👎

t(i18n)`잠시 서버에 문제가 있거나, 일시적인 오류일 수 있습니다.\n 잠시 후 다시 확인해주세요.`;

아래 처럼 변수로 빼야 문자 \n로 인식이 안된다.

Good👍

const BR = '\n';
t(i18n)`잠시 서버에 문제가 있거나, 일시적인 오류일 수 있습니다.${BR} 잠시 후 다시 확인해주세요.`;

문장마다 span 태그가 감싸진 경우 어떻게 하면 될까요?

   <span>삭제 한 후에는 취소할 수 없습니다.&nbsp;</span>
    <b>삭제</b>
    <span>하시겠습니까?</span>

위와 같이 span 태그로 문장마다 감싸진 경우라면, 자연스로운 번역을 위해서 <Trans> 를 가장 바깥으로 감싸야합니다. 그리고 현재 span 으로 문장마다 감싸진 이유는 구글 번역을 위해 추가된거라 제거가 필요합니다.

<Trans>삭제 한 후에는 취소할 수 없습니다. 삭제 하시겠습니까?</Trans>

Trans 와 t(i18n) 은 함께 사용할 수 없습니다.

<Trans>
  삭제 한 후에는 취소할 수 없습니다.
  {isProject('schoolpt') ? t(i18n)`학습 자료` : t(i18n)`콘텐츠`}를 <b>삭제</b> 하시겠습니까?
</Trans>

위와 같이 Trans와 t(i18n) 은 중첩으로 사용이 불가능 한데요, {isProject('schoolpt') ? t(i18n)학습 자료 : t(i18n)콘텐츠} 부분을 변수로 따로 빼서 삽입 해주는 작업이 필요합니다.

const projectText = isProject('schoolpt') ? t(i18n)`학습 자료` : t(i18n)`콘텐츠`;

<Trans>
  삭제 한 후에는 취소할 수 없습니다.
  {projectText}를 <b>삭제</b> 하시겠습니까?
</Trans>;

merge 가이드

기본적으로 작업 브랜치에서 변경된 po 파일 변경사항 보다는 베이스 브랜치 변경사항이 우선순위로 두고 충돌이 나오면 작업 브랜치에서 발생한 변경사항을 무시하고 rebase 한 다음 다시 i18n extract 명령어를 돌려서 갱신한다.

명령어

번역 카탈로그 extract 하기

Exract을 안하면 key(이상한 문자열)가 보이는데 이를 해결하려면 아래 명령어를 돌려 .po 파일 갱신이 필요합니다.

다국어를 적용한 프로젝트 패키지에서 (eg. school-pt, mildang-ui,...)

turbo i18n:extract

하면 locale 폴더에 es-SV 파일이 업데이트 되는데 번역해서 올리거나 그냥 커밋 하면 됩니다.

번역 카탈로그 import 하게 compile

po 파일을 그대로 import 할 수 있지만, 성능을 최적화하기 위해 compile하여 사용

npm run i18n:compile

언어 설정

주소로 locale 설정 방법

쿼리스트링에 hl={localeCode} 추가

예시

https://review.k-aidt.org/lms/group/38542/study-report/230/MjY1ZjQ4NDQtNDNmZC00YTMxLTljMDEtOTJiYzhmYzRlNjBk?hl=es-SV

\n 개행 처리

bad

t(i18n)`잠시 서버에 문제가 있거나, 일시적인 오류일 수 있습니다.\n 잠시 후 다시 확인해주세요.`;

아래 처럼 변수로 빼야 문자 \n로 인식이 안된다.

good

const BR = '\n';
t(i18n)`잠시 서버에 문제가 있거나, 일시적인 오류일 수 있습니다.${BR} 잠시 후 다시 확인해주세요.`;

문장마다 span 태그가 감싸진 경우 어떻게 하면 될까요?

   <span>삭제 한 후에는 취소할 수 없습니다.&nbsp;</span>
    <b>삭제</b>
    <span>하시겠습니까?</span>

위와 같이 span 태그로 문장마다 감싸진 경우라면, 자연스로운 번역을 위해서 <Trans> 를 가장 바깥으로 감싸야합니다.

그리고 현재 span 으로 문장마다 감싸진 이유는 구글 번역을 위해 추가된거라 제거가 필요합니다.

<Trans>삭제 한 후에는 취소할 수 없습니다. 삭제 하시겠습니까?</Trans>

Trans 와 t(i18n) 은 함께 사용할 수 없습니다.

<Trans>
  삭제 한 후에는 취소할 수 없습니다.
  {isProject('schoolpt') ? t(i18n)`학습 자료` : t(i18n)`콘텐츠`}를 <b>삭제</b> 하시겠습니까?
</Trans>

위와 같이 Trans와 t(i18n) 은 중첩으로 사용이 불가능 한데요, {isProject('schoolpt') ? t(i18n)학습 자료 : t(i18n)콘텐츠} 부분을 변수로 따로 빼서 삽입 해주는 작업이 필요합니다.

const projectText = isProject('schoolpt') ? t(i18n)`학습 자료` : t(i18n)`콘텐츠`;

<Trans>
  삭제 한 후에는 취소할 수 없습니다.
  {projectText}를 <b>삭제</b> 하시겠습니까?
</Trans>;

컴포넌트 아닌 곳에서 번역 처리

  • constants.ts 파일 등에서 번역이 필요한 경우
import { I18n } from '@lingui/core';
import { t } from '@lingui/macro';

export const KEYBOARD_TAB_LABELS: (i18n: I18n) => Record<TabKey, string> = (i18n) => ({
  basic: t(i18n)`기본`,
  math: t(i18n)`연산`,
  text: t(i18n)`문자`,
  shape: t(i18n)`도형`,
  unit: t(i18n)`단위`,
});

외부에서 i18n을 받아서 처리해준다.

import { useLingui } from '@lingui/react';
...
  const { i18n } = useLingui();
...
  {KEYBOARD_TAB_LABELS(i18n)[key]}
...

작업 병합 가이드

기본적으로 작업 브랜치에서 변경된 po 파일 변경사항 보다는 베이스 브랜치 변경사항이 우선순위로 두고 충돌이 나오면 작업 브랜치에서 발생한 변경사항을 무시하고 rebase 한 다음 다시 i18n extract 명령어를 돌려서 갱신한다.

언어 선택 우선 순위

  1. hl 쿼리 스트링 (hl 쿼리 스트링이 있으면 NEXT_LOCALE 쿠키에 저장된다.)
  2. NEXT_LOCALE 쿠키
  3. NEXT_PUBLIC_DEFAULT_LOCALE 환경 변수
  4. accept-language 1번째 locale
  5. ko locale