소개
💡 i18n은 국제화 프레임워크로서 다양한 프레임워크나 플랫폼에서 활용 가능하다.
공식 웹사이트 : https://www.i18next.com/
Introduction | i18next documentation
www.i18next.com
여기서는 해당 프레임워크를 Nuxt3와 결합해 프론트엔드 텍스트들을 구글 시트와 통합하여 빌드 시 마다 현지화 텍스트를 자동으로 동기화하고 적용하는 방법을 다룬다. 이 방식을 통해 번역된 텍스트를 더욱 효율적으로 관리 및 유지보수가능하다.
목차
필요 라이브러리 설치
Front-end
- 다국어 처리 프레임워크 i18n
npx nuxi@latest module add i18n
- 구글 인증
yarn add google-auth-library
- 구글 스프레드시트
yarn add google-spreadsheet
구글 시트 API 사용 설정
- 구글 클라우드 콘솔 에서 프로젝트 생성
- Google Sheets API 검색 후 사용 설정
- API/서비스 세부정보에서 사용자 인증정보 만들기 → 서비스 계정 생성
- 계정 클릭 → "키" 탭 → 새 키 생성 → JSON 다운로드
예시 JSON:
{
"type": "service_account",
"project_id": "liahnson",
"private_key": "-----BEGIN PRIVATE KEY-----\ntesttesttest\n——END PRIVATE KEY-----\n",
"client_email": "service-account@test.iam.gserviceaccount.com",
"client_id": "1234",
"token_uri": "https://oauth2.googleapis.com/token"
}
프로젝트 기본 구조화
locales
폴더 생성- 다운받은 JSON 파일을
credentials.json
으로 이름 변경 →locales
폴더에 위치 sync.js
파일 작성 (구글시트 ↔ JSON 동기화)
sync.js 예제
// 모듈 및 전역 변수 선언
const { GoogleSpreadsheet } = require('google-spreadsheet');
const { JWT } = require('google-auth-library');
const credentials = require('./credentials.json'); // 서비스 계정 인증 파일 경로
const fs = require('fs');
const path = require('path'); // 경로 조작을 위한 모듈 추가
const _ = require('lodash');
// 전역 설정
const localesPath = 'locales';
const lngs = ['ko', 'en']; // 언어 목록
const spreadsheetDocId = '1URVacsXhql_XHCj9DQRbvOeb_3Kg1uChB1spkmv3zAQ';
const fileNm = 'translation';
// Google API 인증
const serviceAccountAuth = new JWT({
email: credentials.client_email,
key: credentials.private_key,
scopes: ['<https://www.googleapis.com/auth/spreadsheets>'],
});
// 스프레드시트 로드 함수
async function loadSpreadsheet() {
console.info(
'\\u001B[32m',
'=====================================================================================================================\\n',
`(\\u001B[34mhttps://docs.google.com/spreadsheets/d/${spreadsheetDocId}/#gid=0\\u001B[0m)\\n`,
'=====================================================================================================================',
'\\u001B[0m'
);
const doc = new GoogleSpreadsheet(spreadsheetDocId, serviceAccountAuth);
await doc.loadInfo(); // 문서 정보 로드
return doc;
}
// 시트 데이터 매핑 함수
async function makeTranslationsMapFromSheet(sheet, lngsMap) {
if (!sheet) {
console.error('Sheet not found');
return lngsMap;
}
const rows = await sheet.getRows();
rows.forEach((row) => {
const combinedKey = row._rawData[5]; // Combined key는 해당 위치에 있다고 가정
if (combinedKey) {
lngs.forEach((lang, langIndex) => {
let translation = row._rawData[langIndex + 3]; // ko, en 필드에 접근
if (translation) {
translation = translation.replace(/\\\\n/g, '\\n'); // 이스케이프된 \\n 변환
_.set(lngsMap, `${lang}.${combinedKey}`, translation);
} else {
console.warn(`No translation found for ${lang} in row with combined key: ${combinedKey}`);
}
});
}
});
return lngsMap;
}
// JSON 파일로 저장 함수
function saveTranslationsToFile(lngsMap) {
lngs.forEach((lng) => {
const localeJsonFilePath = path.join(__dirname, `${lng}.json`);
const jsonString = JSON.stringify(lngsMap[lng] || {}, null, 2); // JSON 포맷으로 저장
fs.writeFile(localeJsonFilePath, jsonString, 'utf8', (err) => {
if (err) {
throw err;
}
console.log(`File written successfully: ${localeJsonFilePath}`);
});
});
}
// 메인 업데이트 함수
async function updateJsonFromSheet() {
const doc = await loadSpreadsheet();
let lngsMap = lngs.reduce((acc, lng) => {
acc[lng] = {}; // 언어별 빈 객체 초기화
return acc;
}, {});
for (const sheet of doc.sheetsByIndex) {
console.log(`Processing sheet: ${sheet.title}`);
lngsMap = await makeTranslationsMapFromSheet(sheet, lngsMap);
}
saveTranslationsToFile(lngsMap);
}
// 함수 실행
updateJsonFromSheet();
// 모듈 내보내기
module.exports = {
localesPath,
lngs,
loadSpreadsheet,
updateJsonFromSheet,
fileNm,
};
위 스크립트에 맞춰 아래와 같은 구조로 구글 시트를 작성한다.
페이지명 | 대상 | 원문 | Key(N) | ko (한글번역) |
en (영문번역) |
combinedKey (key1 + 2 + 3) |
---|---|---|---|---|---|---|
Home | 버튼 | Login | - | 로그인 | Login | home.button.login |
Home | 메뉴 | My Page | - | 마이페이지 | My Page | home.menu.mypage |
Manager | 타이틀 | Main Manager | - | 메인관리자 | Main Manager | manager.title.main |
페이지명, 대상, 원문, key1,2,3 : 사용자가 참고하기 위한 데이터
실질적으론 아래의 값이 번역에 사용된다.
ko : 한글번역 데이터
en : 영문번역 데이터
key : i18n 에서 참고하는 키 값 (위 스크립트의 combinedKey에 해당)
그럼 작성한 sync.js 스크립트를 실행하면 아래와 같이 현지화 파일이 생성 혹은 갱신될 것이다.
빌드 시에 현지화 데이터 업데이트 및 빌드를 같이 진행하려면 아래와 같이 스크립트를 작성하면 될 것이다.
package.json script 예시
"scripts": {
"build": "npm run lang:sync && nuxt build",
"lang:sync": "node ./locales/sync.js"
}
프론트엔드 현지화 예시
템플릿에서는 $t('key')
, 스크립트에서는 t('key')
로 사용한다.
언어 변경 시에는 반드시 setLocale 함수를 호출하여 변경하도록 한다.
템플릿 :
{{ $t(mobileTitle) }}
스크립트 :
$alert(t('client.common.passwordHasBeenModified'));
layout/manager.vue
<template>
<div id="manager">
<span>{{ $t(mobileTitle) }}</span>
</div>
</template>
const { setLocale, t } = useI18n();
const mainStore = useMainStore();
const selectedLanguage = computed({
get: () => mainStore.lang,
set: (value) => {
mainStore.setLang(value);
setLocale(value);
document.documentElement.lang = value;
}
});
const mobileTitle = computed(() => {
switch (route.path) {
case '/manager/home':
return 'manager.home.Home';
}
});
const updatePassword = async () => {
try {
await useCustomFetch.put("/common/password", { body: { newPassword, target, email } });
$alert(t('client.common.passwordHasBeenModified'));
} catch (e) {
$toastError(e.message || t('common.toastError.An error occurred while processing the request'));
}
};
'Web Develop > Frameworks and Libraries' 카테고리의 다른 글
[Vue3] ref와 reactive 차이 (0) | 2024.04.03 |
---|---|
[Vue3] Composition API (0) | 2024.04.03 |
[Nuxt.js + Node.js] 백엔드에서 업로드한 정적파일을 프론트에서 참조하기 (0) | 2023.10.22 |
[Nuxt / Node.js] Error occurred while trying to proxy request 날 시에 (0) | 2023.09.19 |
[Vue.js] drag로 요소의 순서 변경하기 (0) | 2023.08.18 |