신상마켓 i18n 개발과정

우리는 K패션을 글로벌로 연결합니다.

이다빈
2022.12.19

K패션 체인저

글로벌 진출이 요즘 딜리셔스에서 가장 핫한 주제 중 하나입니다. 매일 아침 딜리셔스 목표봇이 글로벌 거래액을 알려주고 있습니다. 신상마켓 서비스의 글로벌 진출에 초점을 두어 신상마켓 i18n 작업이 어떻게 진행되었는지 소개합니다.

글로벌 신상마켓

신상마켓은 현재 한국어, 영어, 중국어, 일본어 4개국어로 서비스 되고있습니다.

중국, 대만, 홍콩, 마카오 소매 고객들은 에이전시를 통해 신상마켓을 이용하고 있고,

일본은 지난 8월 31일 도라에몽 프로젝트를 통해 주문, 결제까지 모두 서비스 되고있습니다.

이렇게 글로벌 고객분들은 국제화 작업을 거친 신상마켓을 이용하고 있습니다.

국제화란?

소프트웨어 측면에서 국제화는 특정 지역이나 언어에 종속되지 않고 다양한 지역과 다양한 언어에서 정상 동작하도록 국제적으로 통용되는 소프트웨어를 설계하고 개발하는 과정을 말합니다.
영어로는 Internationalization 이라고 하고, 첫 글자인 i와 마지막 글자인 n 사이의 알파벳 갯수가 18개가 된다고 해서 i18n 이라고 표기하기도 합니다.
국제화는 단순히 언어 번역뿐만 아니라 날짜와 시간 포멧, 통화, 주소 체계등 여러 요소에 적용 된다고 할 수 있습니다.

신상마켓은 국내유저와 해외유저를 구분하는 현지화의 요소와 여러 언어를 지원하는 다국어화의 요소를 모두 포함하고 있는 넓은 의미의 국제화된 서비스라고 볼 수 있습니다.

i18n 라이브러리

신상마켓은 국제화를 위해 i18n 라이브러리를 사용하였습니다.
i18n 라이브러리는 소프트웨어 국제화를 도와주는 라이브러리로, i18n-next가 가장 대표적인데, 신상마켓 프로젝트는 vue.js 프레임워크를 사용하고 있기 때문에 vue.js에서 플러그인 형태로 사용하기 편한 vue-i18n 라이브러리를 사용하였습니다.

기본적으로 i18n라이브러리는 key를 파라미터로 받고 번역된 결과물을 리턴해주는 몇가지 함수를 제공해주는데, 번역뿐만 아니라 복수형처리, 날짜-시간, 통화 포멧의 현지화와 interpolation(보간)처리가 가능합니다.
간단한 예시를 통해 각각 어떻게 처리 되는지 보여드리겠습니다.

기본형 처리

// 번역 데이터
const messages = {
	ko: {
	  slogan : '고객의 패션 사업을 쉽고 즐겁게, 신상마켓',
	},
	en: {
		slogan : 'Sinsang Market makes the customer’s business easy and enjoyable',
	},
};

...

i18n.locale = 'ko';

const text = i18n.t('slogan');
// 고객의 패션 사업을 쉽고 즐겁게, 신상마켓

i18n.locale = 'en';

const text = i18n.t('slogan');
// Sinsang Market makes the customer’s business easy and enjoyable

복수형 처리

// 번역 데이터
const messages = {
  en: {
    car: 'car | cars',
    apple: 'no apples | one apple | {count} apples'
  }
	...
}

const car1 = i18n.tc(car, 1);
// car
const car2 = i18n.tc(car, 2);
// cars

const apple0 = i18n.tc('apple', 0);
// no apples
const apple1 = i18n.tc('apple', 1);
// one apple
const apple5 = i18n.tc('apple', 5, { count: 5 });
// 5 apples

번역 텍스트를 key와 value로 구분하고, 코드에서는 key를 참조하여 사용자가 선택한 locale에 해당하는 value를 노출 하는 방식으로 동작합니다. 또한 갯수에 따라 달라지는 문구가 있다면 갯수를 넘겨줌으로써 단수, 복수 처리가 가능합니다.

날짜 & 시간처리

// 날짜시간 포멧 정의
const dateTimeFormats = {
  "en-US": {
    short: {
      year: "numeric",
      month: "short",
      day: "numeric",
    },
    long: {
      ...생략,
    },
  },
  "ko-KR": {
    short: {
      ...생략,
    },
    long: {
      year: "numeric",
      month: "long",
      day: "numeric",
      weekday: "long",
      hour: "numeric",
      minute: "numeric",
      hour12: true,
    },
  },
};

const enShort = i18n.d(new Date(), "short");
// Oct 5, 2022

const koLong = i18n.d(new Date(), "long", "ko-KR");
// 2022년 10월 5일 수요일 오후3:00

날짜와 시간포멧 처리도 간단히 할 수 있는데요, 우리나라는 년 월 일 순으로 표기하고 미국은 월 일 년순으로 사용합니다. 미리 포멧을 정의해두면 선택된 언어별로 자동처리 할 수 있습니다.

통화 처리

// 숫자포멧 형식 정의
const numberFormats = {
  "ko-KR": {
    currency: {
      style: "currency",
      currency: "KRW",
    },
  },
  "ja-JP": {
    currency: {
      style: "currency",
      currency: "JPY",
    },
  },
};

const won = i18n.n(1000, "currency");
// ₩1,000

const yen = i18n.n(100, "currency", "ja-JP");
// ¥100

i18n의 숫자포멧 처리를 사용해 국가별 통화기호로 나타낼 수 있습니다.

Interpolation

// 번역 데이터
const messages = {
	ko: {
	  text1 : '인증번호를 수신하지 못하는 경우, {0}로 연락해주세요.',
		text2 : '고객센터',
	},
	en: {
		text1 : 'If you cannot receive the verification code, please contact {0}',
		text2 : 'Customer Service',
	},
	...
};

// 템플릿 코드
<i18n path="text1" tag="div">
	<a href="/cs_center" target="_blank"></a>
</i18n>

// 렌더링 결과물
<div>
	인증번호를 수신하지 못하는 경우,
	 <a href="/cs_center" target="_blank">고객센터</a>로 연락주세요.
</div>

번역문 사이에 변수를 넣거나 언어별 어순은 지킨채 문장을 분리해서 처리해야 할 필요가 있을때 유용한 Interpolation 기능입니다. 동적으로 번역문안에 값을 삽입할 수 있습니다.

위 예시처럼 “인증번호를 수신하지 못하는 경우, 고객센터로 연락주세요”라는 문장에서 “고객센터”에 링크를 넣거나 다른 스타일을 적용하고자 태그를 나눠서 처리 할때,

“인증번호를 수신하지 못하는 경우,”

“고객센터”,

“로 연락해주세요.”

이렇게 3가지로 끊어서 번역문구를 받아 처리할 수 있지만, 불완전한 문장과 번역시 어순이 달라질 수 있기 때문에 하나의 문장에 Interpolation 처리를 합니다.

신상마켓의 i18n

애초에 신상마켓 웹은 국제화가 전혀 고려되지 않은 상황에서 개발 되어있었고, 데스크탑 버전만 고려되어 있었습니다. 그러다 보니 코드내에 텍스트로 로직 처리가 되어있는 부분과 반응형 디자인이 고려되지 않은 버튼들로 가득 차 있었습니다. 실제로 번역을 적용해보니 작동하지 않는 로직과 여기 저기 깨진 UI가 보였습니다. 그래서 다국어 번역을 적용 후 프로젝트를 전체적으로 수정할 필요가 있었습니다.

예를 들어서 상품 검색쪽 건물필터 기능이 있는데요, 웹에서는 동대문, 남대문, 부산을 상위 카테고리로 건물을 선택해서 검색할 수 있습니다.

const dongBuildings = items.filter((i) => i.buildingName.includes("동대문"));
const namBuildings = items.filter((i) => i.buildingName.includes("남대문"));
const busanBuildings = items.filter((i) => i.buildingName.includes("부산"));

서버에서 내려주는 전체 건물 리스트에서 건물별로 분류하는 코드입니다.

(예시로 사용하기 위해 단순화한 코드입니다.)

기존 코드는 건물명을 동대문, 남대문, 부산으로 string 검사를 하고 있었습니다. 번역을 적용하면 한국어 이외에 언어는 동작하지 않는 코드가 됩니다.

const dongBuildings = items.filter((i) => i.location === 0);
const namBuildings = items.filter((i) => i.location === 1);
const busanBuildings = items.filter((i) => i.location === 2);

그래서 건물이름으로 검사하는 대신 로케이션 아이디로 건물을 분류하도록 수정하였습니다. 사실 이 부분은 다국어화가 고려되지 않은 프로젝트여도 적용이 되었어야 했던 부분이라고 생각되는데요, 이외에도 i18n 작업이 계기가 되어 레거시 코드를 많이 수정하였습니다.

그리고 디자인적으로도 다국어화가 고려되지 않은 상태로 구현이 되어있었습니다. 장바구니 화면입니다.

버튼의 너비가 한국어 기준으로 구현되어있는 상태에서 UI 수정없이 일본어 번역을 적용하면 한국어와 일본어 문구의 길이가 달라 의도치 않은 UI가 됩니다.

그래서 이런 부분들을 찾아 문구의 길이가 달라져도 어색하지 않도록 수정하는 작업이 필요했습니다.

텍스트 문구 뿐아니라 프로젝트내의 assets도 i18n의 대상입니다. 신상마켓 로고와 딜리셔스 로고는 국내버전과 글로벌 버전이 존재 합니다. 언어에 따라서 다르게 사용되어야 할 assets에 대해서는 현재 선택된 언어에 알맞은 에셋을 리턴하도록 서비스화해서 사용하고 있습니다.

import shinMaLogoKo from '@/assets/images/img-logo-web.svg';
import shinMaLogoEn from '@/assets/images/img-logo-web-en.svg';
...

export default function useGlobalAssets() {
    const shinMaLogo = computed(() => (i18n.locale === 'ko' ? shinMaLogoKo : shinMaLogoEn));
    const dealiciousLogo = computed(() => (i18n.locale === 'ko' ? dealiciousLogoKo : dealiciousLogoEn));
		...

    return {
        shinMaLogo,
        dealiciousLogo,
				...
    };
}

웹화면에 보여지는 모든 문구를 프론트엔드에서만 처리하는건 아닙니다. 카테고리 목록같은경우 백엔드 api를 통해 내려받고 있습니다 이렇게 번역이 필요한 api response일 경우, request 헤더에 사용자가 선택한 언어에 해당하는 언어코드를 같이 넣어 요청하여 서버에서 각 언어에 맞게 번역 처리된 response를 받아 처리합니다.

다국어 작업을 하기 위해선 기본적으로 번역파일이 필요한데요, 번역 파일은 어떤 과정으로 만들어지는지 소개하겠습니다.

다국어 적용 과정 - 구글시트 (deprecated)

신상마켓 번역작업에 사용되었던 구글 시트입니다.

신규 프로젝트가 시작되면 PM이 프로젝트에 추가될 모든 문구들을 추출해서 구글 번역 시트에 추가합니다.
담당언어 번역자분들이 번역 시트를 채워줍니다.
개발자는 구글 시트의 내용을 각각 플랫폼에서 사용하는 포멧에 맞게 추출하여 프로젝트에 적용합니다.
디자이너가 개발환경에 배포된 결과물로 UI를 확인합니다.
번역자가 결과물을 확인합니다.
QA가 빠진 번역이 없는지 확인합니다.
위의 과정을 반복하며 수정합니다.

이런식으로 구글시트를 기반으로 번역을 관리하다보니 여러 문제점이 드러나기 시작했습니다.

하나의 엑셀시트에 여러명의 작업자가 동시에 접근하여 추가와 수정을 하기 때문에 번역 시트를 관리하는 규칙을 따로 만들어야 할 정도 였습니다. 프로젝트가 커지고 다국어 작업의 양이 점점 많아지면서 프로젝트 개발기간중 기능을 개발하는 시간보다 번역물을 관리하고 처리하는 시간이 더 많아지게 되었습니다. 이런 문제들을 해결하고자 구글시트의 대안으로 Lokalise 솔루션을 도입하였습니다.

다국어 적용 과정 - Lokalise

Lokalise는 다국어화 워크플로우를 통합적으로 관리해주는 솔루션입니다.

Lokalise를 사용하면 대시보드에서 프로젝트별로 번역물을 관리하고, 번역 진행상황을 확인할 수 있습니다. 번역 키마다 코멘트 추가가 가능해서 작업자들 간 소통이 용이합니다. 또 해당 번역물이 어디서 사용되는지 화면 스크린샷과 연동되어서 번역자가 어디서 사용되는 문구인지 확인하며 번역 할 수 있습니다. 웹, ios, android 각각 사용하는 플랫폼별로 태깅과 필터링이 가능합니다. 로컬라이즈 프로젝트를 슬랙과 연동하여 번역에 변경이 있을경우 알림을 받을 수 있습니다. 이 외에도 많은 기능들을 제공하고있습니다.

구글시트로 관리했을때와는 달라진 워크플로우입니다.

1) 디자이너가 Figma로 디자인한 결과물에서 로컬라이즈 플러그인을 통해 번역대상물을 추출합니다.

이때 이미 로컬라이즈로 관리되고 있는 문구는 중복해서 생성되지않고, 기존 키로 병합되어 관리 되기 때문에 중복 문구처리를 신경쓰지 않아도 됩니다. PM이 신규로 추가된 문구를 일일히 추출하는 작업이 사라졌습니다.

2) PM이 로컬라이즈 홈에서 추출된 문구를 확인하고 불필요한 문구는 제거한뒤, 번역자에게 전달 합니다.

3) 언어별 번역담당자는 링크된 화면을 확인하며 번역합니다. 어디서 사용되는지 확인후 번역하기 때문에 문맥을 파악한 번역이 가능합니다. 남은 번역 작업에 대한 확인도 명확합니다.

4) 디자이너는 Lokalise 플러그인을 통해 Figma에 번역된 문구를 Import하여 레이아웃을 확인하고 수정합니다.

5) 개발자는 로컬라이즈의 다운로드 기능을 통해 각 플랫폼별 환경에 맞게 번역물을 다운로드 받아 개발에 적용합니다. 번역 결과물은 SDK 연동을 하거나 git 저장소 연동을 통해 최신 번역물을 받을 수 있도록 자동화가 가능합니다. 개발자는 번역물 관리에는 전혀 신경쓰지 않고 번역키만 생성되면 개발을 진행 할 수 있습니다.

6) 번역담당자가 최종 결과물을 검수합니다. Lokalise를 도입하고나서 번역물을 구글시트로 관리했을 때보다 훨씬 효율적인 프로젝트 진행이 가능했습니다.

마무리

이렇게 신상마켓 i18n 개발을 위해 필요한 과정들을 알아보았습니다.

신상마켓 프로젝트의 국제화를 위해 사업팀, 개발팀, 기획팀, 디자인팀 여러 분야의 딜리언즈분들의 손을 거칩니다. 글로벌 패션시장에서 k-패션 체인저라는 이름에 걸맞는 서비스를 할 수 있도록 노력하겠습니다.

(블로그 내용의 이해를 돕기위해 연출된 스크린샷과 매우 간소화 시킨 코드들을 사용하였습니다.)

출처

https://ko.wikipedia.org/wiki/국제화와_지역화

https://www.i18next.com/

https://kazupon.github.io/vue-i18n/

https://lokalise.com/

이다빈

딜리셔스 프론트엔드 개발자

"하늘을 우러러 한점 부끄럼 없는 코드를 쓰자"