소개
💡 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 = 'doc_id_test';
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 | 
