hoony's web study

728x90
반응형

google Sheet를 이용한 다국어 자동화


- 개요

많은 분들이, 이미 i18n을 통한 언어의 국제화는 겪어 보셨을 거라 생각합니다.

하지만, 순수 i18n을 통해 작업을 진행하시다 보면

매번 소스코드 내에, json 파일을 갱신해주면서 생기는 불편함을 느껴보셨을겁니다.

본문에선, 해당 문제점과 불편함을 피하기 위한 방법을 알아보도록 하겠습니다.

 

- 예제

[package.json]

{
  ...
  "devDependencies": {
    "google-spreadsheet": "^3.3.0",
    "@nuxtjs/i18n": "^8.0.0-beta.12",
	...
  },
  ...
}

 

package.json 에서 devDependencies에 필요한 라이브러리를 미리 추가해주도록 합니다.

// 라이브러리 인스톨, 원하시는 패키지매니저를 사용해주시면 됩니다.
npm i
yarn

 

라이브러리 설치 이후, i18n 관련 세팅 부터 먼저 해주도록 하겠습니다.

 

[tsconfig.json]

{
  // https://nuxt.com/docs/guide/concepts/typescript
  "extends": "./.nuxt/tsconfig.json",
  "compilerOptions": {
    "types": [
      "@nuxt/types",
      "@nuxtjs/i18n",
    ]
  }
}

 

프로젝트 루트에 아래와 같이 파일 생성

[i18n.config.ts]

export default defineI18nConfig(() => ({
    legacy: false,
    fallbackLocale: 'ko',
}))

 

[nuxt.config.ts]

 

export default defineNuxtConfig({
    modules: [
        [
            '@nuxtjs/i18n',
            {
                defaultLocale: 'ko',
                strategy: 'prefix_except_default',
                langDir: 'locales/',
                locales: [
                    {code: 'ko', iso: 'ko_KR', file: 'ko/translation.json', name: 'Korean'},
                    {code: 'en', iso: 'en-US', file: 'en/translation.json', name: 'English'},
                ],
                lazy: true,
                vuex: false,
                detectBrowserLanguage: {
                    useCookie: true,
                    cookieKey: "i18n",
                },
                vueI18n: './i18n.config.ts'
            },
        ],
    ],

})

 

modules에 google-spreadsheet 라이브러리를 등록하지 않는 이유는,

nuxt 프로젝트 내부에서 사용하지 않을것이기 때문입니다.

위 nuxt.config.ts 에서 설정한것과 같이, locales 라는 폴더와 그 아래 json 파일을 생성해주도록 하겠습니다.

 

[locales/ko/tranlsation.json]

{
  "common": {
    "login": "로그인",
    "logout": "로그아웃",
    "session": "세션",
    "mypage": "내정보"
  },
  "fruit": {
    "apple": "사과",
    "banana": "바나나"
  }
}

 

[locales/en/translation.json]

{
  "common": {
    "login": "Login",
    "logout": "Logout",
    "session": "Session",
    "mypage": "MyPage"
  },
  "fruit": {
    "apple": "Apple",
    "banana": "Banana"
  }
}

 

이제, i18n이 잘 작동하는지 실행시켜서 확인해보도록 합니다.

 

[app.vue]

<script setup>
const { locale, locales, setLocale } = useI18n()
const config = useRuntimeConfig()

const availableLocales = computed(() => {
  return (locales.value).filter(i => i.code !== locale.value)
})
</script>

<template>
  <div>
<!--    <NuxtWelcome />-->
    {{ $t('fruit.apple') }}
    <br/>
    <br/>
    <br/>
    <a
        href="#"
        v-for="locale in availableLocales"
        :key="locale.code"
        @click.prevent.stop="setLocale(locale.code)"
    >{{ locale.name }}</a
    >
  </div>
</template>

 

위의 과정까지 잘 따라오셨다면, i18n이 정상적으로 작동되는 것을 확인하실 수 있을겁니다.

 

이제, Google Spread Sheet와 연동하여 json을 갱신해줄 차례입니다.

 

 

google-spreadsheet - Google Spreadsheets API -- simple interface to read/write data and manage sheets

 

theoephraim.github.io

그에 앞서, 위의 URL을 통해 Google Developers Console 에서 프로젝트 등록과

서비스 계정 생성 및 Json 파일 다운로드 과정을 먼저 진행해주시기 바랍니다.

* 전체공개 시트를 기준으로 작업하실 경우, 위의 과정은 불필요합니다.

 

저의 경우, 시트와 번역본 json 에 대한 연동 모듈을 프로젝트 내부에 translationmodules 라는 폴더를 생성해

별도로 작업을 진행시켜주었습니다.

 

[translationmodules/index.js]

const {GoogleSpreadsheet} = require('google-spreadsheet');
// Google Developers Console 에서 추가한 서비스어카운트의 Credential 파일
const credentials = require('../i18n-translation-sheet-a991a05e100b.json');

// 번역 파일을 저장할 상위 디렉토리
const localesPath = 'locales';
// 저장될 하위 디렉토리 및 시트에 작성된 언어의 헤더값
const lngs = ['ko', 'en'];
// https://docs.google.com/spreadsheets/d/Spread-Sheet-Doc-ID/edit#gid=Sheet-ID
const spreadsheetDocId = 'Spread-Sheet-Doc-ID';
const fileNm = 'translation';
const sheetId = 'Sheet-ID'; // Sheet-ID

async function loadSpreadsheet() {
    // eslint-disable-next-line no-console
    console.info(
        '\u001B[32m',
        '=====================================================================================================================\n',
        `(\u001B[34mhttps://docs.google.com/spreadsheets/d/${spreadsheetDocId}/#gid=${sheetId}\u001B[0m)\n`,
        '=====================================================================================================================',
        '\u001B[0m'
    );

    // spreadsheet key is the long id in the sheets URL
    const doc = new GoogleSpreadsheet(spreadsheetDocId);

    // load directly from json file if not in secure environment
    await doc.useServiceAccountAuth(credentials);

    await doc.loadInfo(); // loads document properties and worksheets

    return doc;
}

module.exports = {
    localesPath,
    lngs,
    loadSpreadsheet,
    fileNm,
    sheetId,
};

 

[translationmodules/download.js]

 

const fs = require('fs');
const mkdirp = require('mkdirp');
const _ = require('lodash');
const {loadSpreadsheet, localesPath, fileNm, lngs, sheetId} = require('./index');

async function makeTranslationsMapFromSheet(doc) {
    const sheet = doc.sheetsById[sheetId];
    if (!sheet) {
        return {};
    }

    const lngsMap = {};
    const rows = await sheet.getRows();

    rows.forEach((row) => {
        lngs.forEach((lang) => {
            _.set(lngsMap, `${lang}.${row.key}`, row[lang])
        })
    });

    return lngsMap;
}

function makeLocaleDir(dirPath, subDirs) {
    return new Promise((resolve) => {
        subDirs.forEach((subDir, index) => {
            try {
                mkdirp(`${dirPath}/${subDir}`).then(result => {
                    if (result !== undefined) {
                        console.log(`made directories, starting with ${result}`);
                    }
                });
                resolve();
            } catch (err) {
                if(err) {
                    throw err;
                }
            }
        });
    });
}

async function updateJsonFromSheet() {
    await makeLocaleDir(localesPath, lngs);

    const doc = await loadSpreadsheet();
    const lngsMap = await makeTranslationsMapFromSheet(doc);

    fs.readdir(localesPath, (error, lngs) => {
        if (error) {
            throw error;
        }

        lngs.forEach((lng) => {
            const localeJsonFilePath = `${localesPath}/${lng}/${fileNm}.json`;

            const jsonString = JSON.stringify(lngsMap[lng], null, 2);

            fs.writeFile(localeJsonFilePath, jsonString, 'utf8', (err) => {
                if (err) {
                    throw err;
                }
            });
        });
    });
}

updateJsonFromSheet();

 

위와 같이, 별도의 모듈을 만드신 이후

터미널에서 node /translationmodules/download.js

명령어를 통해 실행시켜 보시면, 시트에서 받아와 다운로드 받는것을 확인하실 수 있습니다.

시트는 아래의 사본 기준으로 작성되었습니다.

https://docs.google.com/spreadsheets/d/1jBn3EUumeM7tFLCZE2L6krrXHlr-c7hz2ewx6BM_FNs/edit#gid=0

 

Nuxt i18n 자동화 시트

dictionary ko,en,ja,key,key1,key2,key3,key4,key5,key6,key7 로그인,Login,ログイン,common.login,common,login 로그아웃,Logout,ログアウト,common.logout,common,logout 세션,Session,セッション,common.session,common,session 내정보,MyPage,

docs.google.com

 

혹여나, Google API 라이브러리가 제대로 작동되지 않을경우 서비스 어카운트로 추가한

이메일에게 제한적으로 공유가 되어 있는지 확인 바랍니다.

 

이제 매번 node로 실행시키시기 불편하기에

package.json에 아래처럼 스크립트를 등록시켜서 실행시마다

자동으로 실행되게 해줍니다.

[package.json]

{
  ...
  "scripts": {
    ...
    "i18n:download": "node translationmodules/download.js",
    "dev": "npm run i18n:download && nuxt dev",
    ...
  }
  ...
}

 

728x90

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading