개발하던 프로젝트에 다국어를 지원하기로 했다.
한, 영, 일 3개국어를 대상으로 하고 기존 Text를 다국어처리 해야하는데...
언제 다 바꿀지 부터 막막하다. 처음부터 했으면 좋았을걸...
우선 구현 방식부터 선정해보자.
구현방식
1. 직접 모듈 개발 및 텍스트 표현
보유한 다국어문서를 JSON으로 파싱-각 뷰, 컴포넌트에서 가져다 쓸수 있게 개발하는 방식
장점 - 커스텀이나 유지보수에 용이하고 가볍다.
단점 - 개발공수가 발생하며 사용하는 파일에 import를 하거나 글로벌 모듈로 설정해야한다.
2. 라이브러리 사용 (i18n)
JSON형식의 파일을 사용해서 i18n 라이브러리로 각 컴포넌트에서 언어 변형
장점 - 개발공수가 짧다.
단점 - 커스텀이 제한적이다.
결정
결국 두가지 방법을 조합해서 다음과 같이 사용하기로 했다.
1. 다국어 문서는 구글 스프레드시트를 통해 관리한다.
2. 해당 다국어 문서를 JSON으로 파싱하는 파일을 개발해 사용한다.
3. 생성된 JSON을 i18n로 사용해서 각 파일에서 손쉽게 사용한다.
문서 파싱
구글 스프레드시트를 만들고 GCP(google cloud platform)에서 키와 권한을 받아온다.
스프레드시트를 사용하는 이유는 다음과 같다.
1. 다른 개발자와 협업 시 동시에 작업할 수 있다.
2. 같은 key 또는 텍스트가 곂치는 일을 최소화하고자 실시간 공유가 가능하다.
스프레드시트는 다음과같이 작성한다.
프로젝트에 /locales/script/auto_locailzation.js 파일을 만들어 다음과 같이 구현한다.
const { extractSheets } = require('spreadsheet-to-json');
const fs = require('fs');
const credentials = require('./credentials.json');
extractSheets(
{
spreadsheetKey: '스프레드시트 키',
credentials: credentials,
sheetsToExtract: ['시트1', '시트2'],
},
function(err, data) {
const { 시트1, 시트2 } = data;
const locales = {};
시트1.forEach((item, index) => {
for (const key in item) {
if (key == 'key') continue;
if (locales[key] == undefined) locales[key] = {};
Object.assign(locales[key], { [item.key]: item[key] ?? item.key });
}
});
시트2.forEach((item, index) => {
for (const key in item) {
if (key == 'key') continue;
if (locales[`시트2_${key}`] == undefined)
locales[`시트2_${key}`] = {};
Object.assign(locales[`시트2_${key}`], {
[item.key]: item[key] ?? item.key,
});
}
});
for (const key in locales) {
fs.writeFile(
`locales/json/${key}.json`,
JSON.stringify(locales[key], null, 4),
'utf8',
function(error) {
if (error) {
console.log(`[${key}] error : `, error);
} else {
console.log(`[${key}] : `, 'Success');
}
},
);
}
},
);
이제 GCP에서 획득한 credentials를 json으로 저장해놓는다.
/locales/script/credentials.json
{
"type": "서비스 계정 유형",
"project_id": "GCP 프로젝트 ID",
"private_key_id": "개인 키 ID",
"private_key": "인증용 개인 키",
"client_email": "서비스 계정 이메일",
"client_id": "클라이언트 ID",
"auth_uri": "OAuth 인증 URL",
"token_uri": "토큰 발급 URL",
"auth_provider_x509_cert_url": "공개 인증서 URL",
"client_x509_cert_url": "서비스 계정 인증서 URL",
"universe_domain": "API 도메인"
}
GCP 내에서 스프레드시트 권한을 요청하면서 받은 정보를 모두 입력해주면 된다.
이후 package.json에 이 js파일을 자동으로 실행시켜 줄 명령어를 등록해준다.
/package.json
"scripts": {
"locale": "node locales/script/auto_localization.js"
}
이제 npm run locale 또는 yarn locale을 터미널에 입력해보자.
term
npm run locale
or
yarn locale
위 locale 실행명령어는 스프레드시트를 업데이트 했을때 마다 해주면 된다.
기본시트와 category시트를 사용했기에 언어 별 두가지로 나뉘어서 저장된다.
i18n 라이브러리 사용하기
프로젝트의 라이브러리나 프레임워크마다 사용방식에 조금 차이가 있지만 지금 개발중인 프로젝트가 vue이기때문에
vue를 기준으로 라이브러리를 설치 후 사용한다.
term
yarn add vue-i18n
or
npm install vue-i18n
따로 모듈이나 util을 모아 놓는다면 다음과 같이 파일을 만들어준다.
/src/utils/locale.module.js
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import en from '../../locales/json/en.json';
import ko from '../../locales/json/ko.json';
import ja from '../../locales/json/ja.json';
import category_en from '../../locales/json/category_en.json';
import category_ko from '../../locales/json/category_ko.json';
import category_ja from '../../locales/json/category_ja.json';
// 언어 번역 정의
export const getLocales = () => {
const messageObject = {
ko: { ...ko, ...category_ko },
en: { ...en, ...category_en },
ja: { ...ja, ...category_ja },
};
return messageObject;
};
export const getLanguage = () => {
let userLang = sessionStorage.getItem('langauge') || null;
if (userLang === null) {
userLang = navigator.language;
sessionStorage.setItem('langauge', navigator.language);
}
if (userLang.startsWith('ko')) {
return 'ko';
} else if (userLang.startsWith('en')) {
return 'en';
} else if (userLang.startsWith('ja') || userLang.startsWith('jp')) {
return 'ja';
} else {
return 'ko';
}
};
export const setLanguage = key => {
sessionStorage.setItem('langauge', key);
};
Vue.use(VueI18n);
const messages = getLocales();
export const i18n = new VueI18n({
locale: getLanguage(),
fallbackLocale: 'ko',
messages,
});
export const findKeyByValue = text => {
// 모든 로케일 메시지를 가져옵니다 (ko, en, ja 등)
const locales = ['ko', 'en', 'ja'];
// i18n 리소스를 로케일별로 가져옵니다
const i18nMessages = locales.reduce((acc, locale) => {
acc[locale] = i18n.getLocaleMessage(locale);
return acc;
}, {});
// 키를 찾기 위한 재귀 함수 정의
const findKey = (obj, targetValue, currentPath = '') => {
for (const key in obj) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
if (typeof obj[key] === 'object') {
const result = findKey(obj[key], targetValue, newPath);
if (result) return result;
} else if (obj[key] === targetValue) {
return newPath;
}
}
return null;
};
// 각 로케일에서 해당 문자열의 키를 찾습니다
for (const locale of locales) {
const key = findKey(i18nMessages[locale], text);
if (key) {
return key;
}
}
};
각각 JSON을 라이브러리에서 사용할 수 있게 정리하고 i18n을 초기화 해주는 로직이다.
findByValue 함수는 반대로 key를 찾아오는 함수이며 필요한 경우가 있어 작성했다.
프로젝트에 적용하기
이제 루트파일 (vue는 main.js)에 글로벌 prototype 또는 initialize에 같이 낑궈준다.
/src/main.js
import { i18n } from './utils/locale.module';
... 다른 로직 또는 설정 값
new Vue({
i18n,
render: h => h(App),
}).$mount('#app');
이제 프로젝트에 다국어 설정은 완료되었다.
이제 vue컴포넌트에서 사용해보자.
- 템플릿에서 사용
<template>
<button @click="onClick">
{{ $t('key') }}
</button>
</template>
- 스크립트에서 사용
<script>
export default {
name: 'test',
data() {
model: {
id: 0,
name: '',
}
}
mounted(){
this.model = {
id: 1,
name: this.$t('key');
}
}
}
</script>
vue를 초기화할때 등록했기때문에 i18n의 문법 this.$t('key') 를 이용하면 된다.
템플릿은 this가 기본이기때문에 바로 $t('key')를 사용하면 된다.
물론 해당 key를 서버에서 받는 경우에는 $t(value)와 같이 사용해도 된다.
이제 기존 텍스트를 모두 찾아서 단순 반복 노동을 시작해볼까..?
'고민, 이슈 정리 > 이슈, 버그' 카테고리의 다른 글
[해결]moment.js deprecated이슈로 인해 datetime 라이브러리 교체 (feat. day.js) (0) | 2023.08.29 |
---|---|
[해결] Invalid date cross-browsing 날짜객체 에러(크로스 브라우징) (0) | 2023.08.24 |
[해결] Next.js GCP AppEngine 배포 환경변수 분기 (0) | 2023.08.11 |
[미해결] React Infinite UpScrolling (0) | 2023.05.30 |
[해결] Socket.io 웹뷰 접속지연 (0) | 2023.05.16 |