i18next react에서 사용하기

 

1탄에서 열심히 json 파일 자동화 시켰으니까 이제 사용하는 프로젝트에 연동하면 됩니다.

기본 골자는 아래와 같습니다.

 

1. i18n.js 파일 만들기

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
// LanguageDetector 는 다양한 방법으로 사용자 pc에서 기본 언어를 감지해 설정한다
// 자세한 내용은 아래에 후술하겠음

import translationEN from "../locale/en/en.json";
import translationKO from "../locale/ko/ko.json";
// 위처럼 그냥 json 가져와도 잘 읽힌다

const resources = {
  en: {
    translation: translationEN,
  },
  ko: {
    translation: translationKO,
  },
};

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    fallbackLng: "ko", // 번역 파일에서 찾을 수 없는 경우 기본 언어
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;

 

2.최상위 파일에서 i18n.js 빌드하기

import React from 'react'
import App from './App.jsx'
import "./locale/i18n.js" // 최상위 파일에서 build 해준다

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

 

3. 다국어 지원을 할 컴포넌트에서 사용

import { useTranslation } from "react-i18next";

export default function I18nTest() {
  const { t, i18n } = useTranslation();

  // 언어 변경하기
  const changeLanguage = (lang) => {
    i18n.changeLanguage(lang); 
  };

  return (
    <div>
      <div>
        <button onClick={() => changeLanguage("ko")}>한국어</button>
        <button onClick={() => changeLanguage("en")}>English</button>
      </div>
      <h1>{t(`auto-translation`)}</h1> // json 파일에 설정했던 key 값으로 불러온다
      <div>{t(`new-jeans`)}</div> // json 파일에 설정했던 key 값으로 불러온다
    </div>
  )
}

 

이번에 조금 더 딥하게 공부하면서 i18next의 디테일에 놀란 부분이 몇개 있다

 

첫번째는 i18next-browser-languagedetector 의 기능

무려 5가지 방법을 통해 사용자 언어를 감지해준다 그리고 i18n.changeLanguage() 로 언어를 변경하면 마지막으로 변경한

언어를 기억해서 다시 접속하면 해당 언어를 default로 보여준다. 

(electron 으로 개발할 때는 브라우저랑 좀 달라서 그런지 마지막 변경 기억을 안해줘서 따로 코드로 구현했었음..)

 

1. 쿼리 문자열

 - URL의 쿼리 문자열에서 언어를 감지. 예를 들어, ?lng=en과 같은 형식으로 언어를 지정.

2. 로컬 스토리지

 - 로컬 스토리지에서 언어를 감지. 쿠키와 유사하게 사용자 언어 설정을 저장하고 나중에 불러오는데 사용.

3. 쿠키

 - 이전에 설정된 언어를 쿠키에서 감지. 사용자가 이전에 선택한 언어 설정을 기억

4. 세선 스토리지

 - 세선 스토리지에서 언어 감지

5. 브라우저 설정

 - 브라우저 navigator.language 또는 navigator.languages 속성을 통해 사용자의 기본 언어를 감지.

 

나의 경우는 i18n.changeLanguage() 메서드로 언어를 변경할때마다 localStorage에 저장되고 있었다.

 

두번째는 useTranslation 훅

언어가 변경될 때마다 useTranslation 훅을 가진 컴포넌트를 리랜더링 해주는게 신기해서 안을 좀 까봤다.

useTranslation 안에 아래 useState로 상태관리를 해주고 있었고
const [t, setT] = react.useState(getT);

ns(언어) 가 변경되는걸 트리거로 useEffect 에서 setter 함수로 state 변경해서 랜더링 해주고 있었다.

react.useEffect(() => {
  // 생략

  // 이전 ns 과 현재 ns 가 다른 경우에 setter 해주고 있음
  if (ready && previousJoinedNS && previousJoinedNS !== joinedNS && isMounted.current) {
    setT(getNewT); 
  }

  // 생략
}, [i18n, joinedNS]); // ns 로 트리거

 

docs 도 깔끔하고 커스텀해서 사용하기도 좋고 왜 많이 쓰는지 다시한번 납득할수 있었다

우리 회사도 글로벌인데 영어 버전도 지원해야지!

 

언제부턴가 다국어 지원이 흔한 기능이 되었다.

전에 사이드프로젝트 할때 i18n 써서 했던거라 대수롭지 않게 생각했는데 현업해서 해보니 생각보다 협업하기 어려웠고 절차가 번거로웠다. 우리는 i18n을 json 파일로 영문 전환 버튼으로 ko.json / en.json 바꿔서 적용했는데

자동화하기 전에 절차가 어땠냐면, 

 

1. 개발하다가 ko.json 에 key value 를 추가한다

2. 검수하시는 직원께 메신저로 검수 요청을 보낸다

3. 확인 및 검수 후 답장을 보낸다

4. 검수 받은 영문을 en.json 에 key value 를 추가한다

 

위 절차가 번역물이 업데이트가 종종 일어날때마다 진행되니 번거로운건 둘째치고 업무 효율이 매우 낮아졌다.

(검수하시는 분도 본인 업무도 많은데 자꾸 요청드려서 미안했다..)

그렇게 몇번의 업데이트 후 다른거 개발하기도 바쁜데 번역에 업무시간을 자꾸 빼앗기기 싫어서 자동화하기로 했다.

 

구글 스프레드시트를 통한 다국어 번역 자동화하기

 

타 블로그에 자세히 정리도 되어있고 방법도 상세한데 나는 좀 과하게 자동화한 느낌도 나서.. 나름 간소화 버전으로 만들었는데 다른 분들 입맛에 맞을지는 모르겠다.

 

(완성본 프리 뷰)

번역물 ko.json 에 추가

 

package.json 에 npm 명령어로 js 파일 실행
구글 스프레드시트에 자동으로 업로드, 위 화면 중 검수자는 자동 번역이 별로면 검수 열에 입력하면 자동 반영

 

npm 명령어로 다운로드 받아 en.json 파일 업데이트

 

위의 방식이다. 결국 컨셉은 1. ko.json 파일을 npm 명령어로 구글 스프레드시트에 업로드 2. 검수자는 구글 스프레드시트 화면만 바라보면 되고 3. 검수 됐다는 답장오면 한번씩 npm run download en.json 을 업데이트 해주면 된다.

 

절차 단계가 줄어든건 아니지만 업데이트 된 번역물만 뽑아서 검수자에게 전달하고 검수된 내용을 json 파일로 복붙하던 DX 개선과 특히, 같은 스프레드시트 화면을 바라보는 검수자와의 커뮤니케이션 비용은 확실하게 개선되었다.  

 

아래부터는 구글 스프레드 시트와 연동하여 다국어 번역 절차를 간소화하는 방법을 서술하겠습니다.

 

1. 구글 스프레드시트 서비스 계정 만들기

구글 클라우드 대시보드 요 링크 타고 들어가서 아래 <서비스 계정> 클릭

 

2. 계정 이름 정도만 입력하고 완료 누르면 된다. 

 

 

3. 만들어진 서비스 계정 옆에 편집 버튼을 눌러 키를 추가한다 (키 유형 JSON)

※ 만들어진 json 파일은 api 사용할때 인증해야되니 저장해두자

 

4. api로 컨트롤할 스프레드시트 만들기

구글 스프레드시트 바로가기

링크 타고 빈 스프레드시트 하나 만들어준다.

아래 밑줄 친게 5번에서 사용할 process.env.SPREAD_SHEET_DOC_ID 부분이다.

공유 버튼 눌러서 만들어 두었던 사용자 계정 이메일을 등록!

참고로)

자동번역 열에 등록해 놓은 함수는 =IF(B2<>"",GOOGLETRANSLATE(B2, "ko", "en"),"")

다운로드 열에 등록해 놓은 함수는 =IF(D2<>"", D2,C2)

 

 

5. upload.js 만들기

npm i google-spreadsheet
npm i google-auth-library
npm i dotenv // (option!)
import dotenv from "dotenv";
dotenv.config();

import { GoogleSpreadsheet } from "google-spreadsheet";
import { JWT } from "google-auth-library";
import creds from "./translation/.credentials/august-sandbox-378002-e13c063fc08e.json" assert { type: "json" };

// 위에 import 는 3번에서 만든 json key 에서 불러온것임!

import fs from "fs";

const serviceAccountAuth = new JWT({
  email: creds.client_email,
  key: creds.private_key,
  scopes: [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive.file",
  ],
});

const doc = new GoogleSpreadsheet(
  process.env.SPREAD_SHEET_DOC_ID,
  
  // SPREAD_SHEET_DOC_ID 는 4번에서 밑줄친 부분!
  
  serviceAccountAuth
);

await doc.loadInfo();
console.log(doc.title);

const sheet = doc.sheetsByIndex[0]; // 난 첫번째 시트가 번역 시트여서 [0] 설정한 것

await sheet.setHeaderRow([ // 첫번째 행 header 설정 해주고 
  "key",
  "한글",
  "자동번역",
  "검수",
  "비고",
  "다운로드",
]);

const rows = await sheet.getRows();

let existedKeys = []; // 이미 시트에 등록된 key는 제외해주려고 배열에 담음
for (const row of rows) {
  existedKeys.push(row._rawData[0]);
}

const filePath = process.env.KO_JSON_PATH; // 사용하실 분들은 이 경로 설정만 하시면 됩니다
const jsonFile = fs.readFileSync(filePath);
const jsonData = JSON.parse(jsonFile);

// json key의 depth를 .으로 평탄화하는 재귀함수
function flattenObject(obj, parentKey = "", result = []) { 
  for (const [key, value] of Object.entries(obj)) {
    const newKey = parentKey ? `${parentKey}.${key}` : key;
    if (typeof value === "object" && value !== null && !Array.isArray(value)) {
      flattenObject(value, newKey, result);
    } else {
      const newObject = { key: newKey, 한글: value };
      result.push(newObject);
    }
  }
  return result;
}

const flattenedKoJson = flattenObject(jsonData);

// 이미 있는건 업데이트 해주지 않습니다. (있던 내용을 바꾸려면 그냥 시트에서 바꾸세요!)
const newFlattenedKoJson = flattenedKoJson.filter(
  (obj) => !existedKeys.includes(obj.key)
);

console.log("새롭게 업데이트 될 Array: ", newFlattenedKoJson);

if (newFlattenedKoJson.length > 0) {
  await sheet.addRows(newFlattenedKoJson);
}

console.log("Uploaded successfully");

 

 

6. download.js 만들기

import dotenv from "dotenv";
dotenv.config();

import { GoogleSpreadsheet } from "google-spreadsheet";
import { JWT } from "google-auth-library";
import creds from "./translation/.credentials/august-sandbox-378002-e13c063fc08e.json" assert { type: "json" };
import fs from "fs";
import { dirname } from "path";

const serviceAccountAuth = new JWT({
  email: creds.client_email,
  key: creds.private_key,
  scopes: [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive.file",
  ],
});

const doc = new GoogleSpreadsheet(
  process.env.SPREAD_SHEET_DOC_ID,
  serviceAccountAuth
);

await doc.loadInfo();

const sheet = doc.sheetsByIndex[0];

const rows = await sheet.getRows();
const fetchedRows = rows.map((row) => {
  return row._rawData;
});

function arrayToNestedObject(arr) {
  const result = {};

  arr.forEach(([path, , , , , value]) => {
    const keys = path.split(".");
    let current = result;

    keys.forEach((key, index) => {
      if (index === keys.length - 1) {
        current[key] = value;
      } else {
        if (!current[key]) {
          current[key] = {};
        }
        current = current[key];
      }
    });
  });

  return result;
}

const nestedObject = arrayToNestedObject(fetchedRows);

const filePath = process.env.EN_JSON_PATH; // 마찬가지로 사용하실 분들은 요 경로를 설정해주시면 됩니다!
// 중간 폴더 없으면 생성
fs.mkdirSync(dirname(filePath), { recursive: true });
// 파일 생성
fs.writeFileSync(filePath, JSON.stringify(nestedObject, null, 2));

console.log("Downloaded successfully");

 

담백하게 적어서 금방 끝나버렸는데 내용이나 컨셉 설명은 타 블로그에 정리 잘된게 많으니 아래 링크를 보시는걸 추천드립니다.

https://velog.io/@calvinsnax/getting-i18n-with-google-spreadsheet

 

구글스프레드시트를 200% 활용한 국제화(i18n) 자동화 사례

비개발자와의 협업이 유독 중요했던 국제화 프로젝트. 구글스프레드시트를 활용하여 비개발자와의 협업을 멋지게 완성해낸 국제화(i18n) 자동화 사례를 소개합니다.

velog.io

https://www.wooslog.com/blog/internalization-automation

 

구글 스프레드시트를 통한 국제화 자동화

뤼튼에서는 글로벌한 유저들에 대비하기 위해 국제화를 진행하였습니다. 국제화는 한국어로 된 문구들을 다른 나라 언어로도 제공하는 기능을 말합니다.

www.wooslog.com

 

2탄은 react 에 적용하는 부분입니다:)

여기까지만 봐도 핵심 내용은 끝이긴하지만..

 

+ Recent posts