🌍 Angular에서 i18next를 사용한 번역 설정
1. 프로젝트 설정
- i18next 관련 라이브러리 설치
npm install i18next --save
npm install angular-i18next --save
npm install --save-dev i18next-scanner
2. i18next 설정
- i18next 설정 파일 생성 (i18next.config.js)
번역 키를 스캔하고 변환할 설정 파일을 작성합니다.
module.exports = {
input: ['src/**/*.html', 'src/**/*.ts'], // 번역할 소스 경로 설정
output: './src/assets/locales', // 추출된 번역 파일의 저장 위치
options: {
removeUnusedKeys: true, // 사용하지 않는 키 제거
lngs: ['en', 'ko'], // 지원할 언어 설정
defaultLng: 'en', // 기본 언어 설정
resource: {
loadPath: './src/assets/locales/{{lng}}.json',
savePath: './src/assets/locales/{{lng}}.json'
}
}
};
3. Angular에서 번역 모듈 설정
- i18next 초기화 (app.module.ts)
i18next와 @ngx-translate를 초기화하여 Angular에서 사용할 수 있도록 설정합니다.
import i18next from 'i18next';
import HttpBackend from 'i18next-http-backend';
import { NgModule } from '@angular/core';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { I18NextModule, I18NextLoadService } from 'angular-i18next';
i18next
.use(HttpBackend)
.init({
fallbackLng: 'en',
debug: true,
backend: {
loadPath: '/assets/locales/{{lng}}.json',
},
});
@NgModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (http: HttpClient) => new I18NextLoadService(http),
deps: [HttpClient],
},
}),
I18NextModule.forRoot(),
],
})
export class AppModule {}
2. 번역키 사용하기
<div>{{ 'home.title' | translate }}</div>
4. Google 스프레드시트 연동
- 번역 파일 업로드 및 업데이트
- i18next-scanner와 Google Sheets API를 이용하여 번역 데이터를 스프레드시트에 저장합니다.
- Google Cloud 에 로그인
- Google Cloud 접속하고 로그인합니다.
- 새 프로젝트를 생성하거나 기존 프로젝트를 사용합니다.
- 서비스 계정 생성
- API 및 서비스 > 사용자 인증 정보로 이동합니다.
- 사용자 인증 정보 만들기 > 서비스 계정을 생성합니다.
- 서비스 계정이 생성되면 키 탭으로 이동하여 새 키 만들기를 클릭하고 JSON 키 파일을 다운로드합니다.
Google 클라우드 플랫폼
로그인 Google 클라우드 플랫폼으로 이동
accounts.google.com
5. 번역 업데이트 및 자동화
- Google 스프레드시트에서 번역 파일 가져오기
- 스프레드시트에 저장된 번역을 JSON 파일로 변환하여 src/assets/locales에 저장합니다.
- 번역 데이터 업데이트 시, Google API를 통해 스프레드시트와 로컬 파일을 동기화합니다.
- download.js / index.js 등 업로드 관련 js 세팅
import fs from "fs";
import { mkdirp } from "mkdirp";
import {
loadSpreadsheet,
localesPath,
ns,
lngs,
sheetId,
columnKeyToHeader,
NOT_AVAILABLE_CELL,
} from "./index";
import { GoogleSpreadsheet } from "google-spreadsheet";
/**
* fetch translations from google spread sheet and transform to json
* @param {GoogleSpreadsheet} doc GoogleSpreadsheet document
* @returns [object] translation map
* {
* "ko-KR": {
* "key": "value"
* },
* "en-US": {
* "key": "value"
* },
* }
*/
const fetchTranslationsFromSheetToJson = async (
doc: GoogleSpreadsheet
): Promise<{ [key: string]: { [key: string]: string } }> => {
const sheet = doc.sheetsById[sheetId];
if (!sheet) {
return {};
}
const lngsMap: { [key: string]: { [key: string]: string } } = {};
const rows = await sheet.getRows();
rows.forEach((row) => {
const key = row.get(columnKeyToHeader.key);
lngs.forEach((lng: string | number) => {
const translation = row.get(columnKeyToHeader[lng]);
console.log({ lng });
console.log({ translation });
// NOT_AVAILABLE_CELL("_N/A") means no related language
if (translation === NOT_AVAILABLE_CELL) {
return;
}
if (!lngsMap[lng]) {
lngsMap[lng] = {};
}
lngsMap[lng][key] = translation || ""; // prevent to remove undefined value like ({"key": undefined})
});
});
return lngsMap;
};
const checkAndMakeLocaleDir = async (
dirPath: string,
subDirs: string[]
): Promise<void> => {
for (let i = 0; i < subDirs.length; i++) {
const subDir = subDirs[i];
const path = `${dirPath}/${subDir}`;
try {
await mkdirp(path);
} catch (err) {
throw err;
}
}
};
const updateJsonFromSheet = async (): Promise<void> => {
await checkAndMakeLocaleDir(localesPath, lngs);
const doc = await loadSpreadsheet();
const lngsMap = await fetchTranslationsFromSheetToJson(doc);
fs.readdir(localesPath, (error, lngs) => {
if (error) {
throw error;
}
lngs.forEach((lng) => {
const localeJsonFilePath = `${localesPath}/${lng}/${ns}.json`;
const jsonString = JSON.stringify(lngsMap[lng], null, 2);
fs.writeFile(localeJsonFilePath, jsonString, "utf8", (err) => {
if (err) {
throw err;
}
});
});
});
};
updateJsonFromSheet();
반응형
'Angular' 카테고리의 다른 글
Angular - 뒤로 가기 방지 관련 이슈 및 해결 방법 (history) (2) | 2024.11.01 |
---|---|
Angular - ControlValueAccessor (0) | 2024.09.12 |
Angular - 동작원리 및 RxJS와 Ngrx (0) | 2024.08.22 |
Angular Google Maps (AGM) 사용법 (1) | 2024.07.11 |
Angular 유닛 테스트: Jasmine vs Jest 비교 (0) | 2023.05.23 |