본문 바로가기

개발/Vue

[VUE3+Google Spreadsheet] 다국어 적용 (vue-i18n)

다국어 적용 진행 과정을 간단하게 기록한다.


1. 기본 다국어 처리

기본적인 다국어 처리

 vue-i18n문서블로그 를 참고하여 진행했다.

 

- npm 설치

npm install vue-i18n@9
npm i --save-dev @intlify/unplugin-vue-i18n

 

- 다국어 파일

// ko.json
{
  "changeLocale": "지역변경",
  "title": "제목",
  "doc": "문서",
  "tooling": "다듬다",
  "ecosystem": "에코시스템",
  "community": "커뮤니티",
  "support": "지원",
  "youdidit": "해냈구나!",
  "route": {
    "home": "홈",
    "about": "어바웃"
  },
  "test": "테스트"
}

// en.json
{
  "changeLocale": "change Locale",
  "title": "Title",
  "doc": "Documentation",
  "tooling": "Tooling",
  "ecosystem": "Ecosystem",
  "community": "Community",
  "support": "Support Vue",
  "youdidit": "You did it!",
  "route": {
    "home": "Home",
    "about": "About"
  },
  "test": "Test"
}

 

- 설정

// i18n/index.js
import ko from "@/locales/ko.json"
import en from "@/locales/en.json"
import { createI18n } from "vue-i18n";

const i18n = createI18n({
    locale: import.meta.env.VITE_DEFAULT_LOCALE,
    fallbackLocale: import.meta.env.VITE_FALLBACK_LOCALE,
    legacy: false,
    messages: {
        ko,
        en
    }
})

export default i18n;

 

- 기본 활용1 (template 내부 활용)

// App.vue
<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="youdidit" />
      <nav>
        <RouterLink to="/">{{ $t('route.home') }}</RouterLink>
        <RouterLink to="/about">{{ $t('route.about') }}</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

 

- 기본 활용2 (template 및 js 활용)

// TheWelcome.vue
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'

import {useI18n} from "vue-i18n";
const {  locale } = useI18n({ useScope: 'global' })
const i18n = useI18n();

const changeLocale = () =>{
  locale.value = locale.value === "en" ? "ko": "en";
}

</script>

<template>
<div>
  <button @click="changeLocale">{{ $t('changeLocale') }}</button>
</div>
  <WelcomeItem>

    <template #icon>
      <DocumentationIcon />
    </template>
    <template #heading>{{ $t('doc') }}</template>
   // 줄임
  </WelcomeItem>

  <WelcomeItem>
    <template #icon>
      <ToolingIcon />
    </template>
    <template #heading>{{ $t('tooling') }}</template>
   // 줄임
  </WelcomeItem>

  <WelcomeItem>
    <template #icon>
      <EcosystemIcon />
    </template>
    <template #heading>{{ $t('ecosystem') }}</template>
	// 줄임
  </WelcomeItem>

  <WelcomeItem>
    <template #icon>
      <CommunityIcon />
    </template>
    <template #heading>{{ $t('community') }}</template>
	// 줄임
  </WelcomeItem>

  <WelcomeItem>
    <template #icon>
      <SupportIcon />
    </template>
    <template #heading> {{ $t('support') }}</template>
    // 줄임
  </WelcomeItem>
</template>

 

2. 심화 다국어 처리

페이지 타이틀 동적 처리를 위한 라우터 처리

 

- Router Page Title 처리

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import {nextTick, computed} from "vue";
import i18n from "../i18n";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('../views/HomeView.vue'),
      meta:{title: 'home'}
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue'),
      meta:{title:'about'}
    }
  ]
})

const DEFAULT_TITLE = 'TEST';
router.afterEach(to => {
  const title = to.meta.title ? i18n.global.t('route.'+to.meta.title) : DEFAULT_TITLE;
  nextTick(()=> document.title = title)
})


export default router

 

3. 구글 스프레드 시트와 연동하여 다국어 파일 자동화 처리

협업이 가능한 다국어 파일 생성

google-spreadsheet 문서와 블로그를 참고하여 진행했다.

 

- 스프레드 시트 생성 및 용어 정리

 

npm test

시트1 ko,en,ja,key,key1,key2,key3,key4,key5,key6,key7 지역변경,change Locale,changeLocale,changeLocale 제목,Title,title,title 문서,Documentation,doc,doc 다듬다,Tooling,tooling,tooling 에코시스템,Ecosystem,ecosystem,ecosystem 커뮤니티,

docs.google.com

 

- npm 설치

npm i google-spreadsheet

 

- 설정 파일

구글에서 제공한 문서를 통해 인증 절차를 거치거나 시트 권한 설정 없이 진행 가능

// translationmodules/index.js
const {GoogleSpreadsheet} = require('google-spreadsheet');
const {JWT} = require('google-auth-library')

// Google Developers Console 에서 추가한 서비스어카운트의 Credential 파일 (이 파일은 .gitignore 처리)
const credentials = require('../src/assets/config/swift-reef-398705-e0f0b6e8a1c5.json');

// 번역 파일을 저장할 상위 디렉토리
const localesPath = 'locales-test';

// 저장될 하위 디렉토리 및 시트에 작성된 언어의 헤더값
const lngs = ['ko', 'en'];

// https://docs.google.com/spreadsheets/d/Spread-Sheet-Doc-ID/edit#gid=Sheet-ID
const spreadsheetDocId = '10p132F_SYazLRTno7sqSTkMmlYJZ7KhucgnLhQrDJBw';
const fileNm = 'translation';
const sheetId = '0'; // 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'
    );
    const SCOPES = [
        'https://www.googleapis.com/auth/spreadsheets',
    ];

    const jwt = new JWT({
        email: credentials.client_email,
        key: credentials.private_key,
        scopes: SCOPES,
    });

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

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

    return doc;
}

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

 

- 다운로드 파일
참고한 블로그의 소스를 거의 그대로 썼는데 구글시트 npm 버전이 달라서 방식이 바뀌었는지
makeTranslationsMapFromSheet 함수 내부의 row 데이터를 불러오는 방식을 조금 수정해야 했다.

// 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.get('key')}`, row.get(lang))
        })
    });

    return lngsMap;
}

function makeLocaleDir(dirPath, subDirs) {
    return new Promise((resolve) => {
        subDirs.forEach((subDir, index) => {
            try {
                mkdirp.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) {
            console.error('디렉토리 읽기 오류:', 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) {
                    console.error('파일 쓰기 오류:', err);
                    throw err;
                }
            });
        });
    });
}

updateJsonFromSheet();

 

- 스크립트 추가

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

 

- 실행 및  json 확인

 

테스트 소스

 

GitHub - bom2zzang/vue-i18n-test

Contribute to bom2zzang/vue-i18n-test development by creating an account on GitHub.

github.com