Angular에서 간단하게 번역기능 구현하기

Angular에서 간단하게 번역기능 구현하기

이글은 Angular에서 번역을 구현하는 방법에 대해 설명합니다.

소개

이 글에서 다음과 같은 방법을 배웁니다.

  1. 아래와 같이 HTML 뷰에서 단어를 번역하는데 사용할 수있는 Pipe를 만듭니다.

    1
    2
    <!-- should display 'hola mundo' when translate to Spanish -->
    <p>{{ 'hello world' | translate }}</p>
  2. 아래와 같이 JS/Typescript로 단어를 번역할 때 사용할 수 있는 Service를 만듭니다.

    1
    2
    3
    4
    ...
    // should display 'hola mundo' when translated to Spanish
    this.translatedText = this.translate.instant('hello world'); // this.translate is our translate service
    ...
  3. 여러 번역 (예 : 영어, 스페인어, 중국어 등)을 정의한 다음 현재 언어를 설정할 수 있습니다.

    1
    2
    3
    ...
    this.translate.use('es'); // use spanish
    ...
  4. 번역을 위한 Placeholder 사용할 수 있습니다. 예를 들어, 아래와 같이 번역하고 싶다고 가정 해보십시오.

    1
    Hello, [first_name] [last name]!

다음과 같이 변환 값을 정의할 수 있어야합니다.

1
2
3
4
// translation definition, %<number> as placeholder
...
'hello greet': 'Hello %0 %1'
...

우리는 다음과 같이 translate Service를 사용할 수 있기를 원합니다.

1
this.translate.instant('hello greet', [customer.firstName, customer.lastName]);

HTML View도 마찬가지입니다. 매개 변수를 전달할 수 있어야합니다.

1
<p>{{ 'hello greet' | translate: [customer.firstName, customer.lastName] }}</p>

  1. 새로 고침 텍스트와 같은 기능을 실행하기 위해 언어가 변경 될 때마다 Publish하고 Subscribe합니다.

  2. 기본 언어를 사용하고 대비책을 사용하도록 설정합니다. 예를 들어 주어진 사용자가 현재 언어를 ‘중국어’로, 기본 언어를 ‘영어’로 설정합니다. 하지만 중국어에서 ‘hello’라는 단어를 찾을 수 없으면 영어 번역 정의에서 대체 단어를 찾아야합니다.

아래는 우리가 만들 UI입니다.

요구사항

  1. 사용자가 언어 버튼을 클릭하면 그에 따라 번역이 업데이트되어야합니다.

App 설정

다음은 파일 구조입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|- app/
|- app.component.html
|- app.component.ts
|- app.module.ts
|- main.ts
|- translate/
|- index.ts
|- lang-en.ts
|- lang-es.ts
|- lang-zh.ts
|- translate.pipe.ts
|- translate.service.ts
|- translation.ts
|- index.html
|- systemjs.config.js
|- tsconfig.json

설명

  1. 모든 translate 관련 파일을 위해 app 폴더 아래에 translate 폴더를 만들었습니다.
  2. lang-[name].ts 파일은 번역 정의를 보관하는 곳입니다. 이 예에서는 영어(en), 스페인어(es) 및 중국어(zh)를 사용합니다.
  3. translations.ts는 모든 번역을 연결하는 곳입니다.
  4. translate.pipe.tstranslate.service.ts는 우리 ServicePipe의 파일입니다.
  5. index.ts는 Barrel로 export하기 위한 파일입니다. 나중에 Barrel에 대한 자세히 알아보겠습니다.

번역 정의 추가

lang-[name] 형식의 파일이름에 번역을 추가하겠습니다.

1
2
3
4
5
6
7
// lang-en.ts
export const LANG_EN_NAME = 'en';
export const LANG_EN_TRANS = {
'hello world': 'hello world',
};
1
2
3
4
5
6
7
// lang-es.ts
export const LANG_ES_NAME = 'es';
export const LANG_ES_TRANS = {
'hello world': 'hola mundo',
};
1
2
3
4
5
6
7
// lang-zh.ts
export const LANG_ZH_NAME = 'zh';
export const LANG_ZH_TRANS = {
'hello world': '你好,世界',
};

번역

이제 translation.ts 파일의 모든 번역 파일을 연결합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/translate/translation.ts
import { OpaqueToken } from '@angular/core';
// import translations
import { LANG_EN_NAME, LANG_EN_TRANS } from './lang-en';
import { LANG_ES_NAME, LANG_ES_TRANS } from './lang-es';
import { LANG_ZH_NAME, LANG_ZH_TRANS } from './lang-zh';
// translation token
export const TRANSLATIONS = new OpaqueToken('translations');
// all translations
const dictionary = {
[LANG_EN_NAME]: LANG_EN_TRANS,
[LANG_ES_NAME]: LANG_ES_TRANS,
[LANG_ZH_NAME]: LANG_ZH_TRANS,
};
// providers
export const TRANSLATION_PROVIDERS = [
{ provide: TRANSLATIONS, useValue: dictionary },
];

설명

  1. 모든 번역 정의 파일을 가져 왔습니다.
  2. 우리는 translations라는 Opaque 토큰을 만들었습니다. Opaque 토큰은 응용 프로그램 인터페이스가 없는 오브젝트입니다. 이것은 의존성 주입에 사용되는 특별한 종류의 Provider lookup key입니다. 자세한 내용은 Angular 공식 문서를 참조하십시오.
  3. dictionary 변수는 모든 번역을 링크합니다.
  4. TRANSLATION_PROVIDERS는 앞에서 정의한 Opaque 토큰을 사용하고 dictionary을 값으로 제공한다고 설명합니다. 나중에 부트스트랩 (main.ts)에 TRANSLATION_PROVIDERS를 등록할 것입니다.

Translate Service

Service를 만들어 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// app/translate/translate.service.ts
import {Injectable, Inject} from '@angular/core';
import { TRANSLATIONS } from './translations'; // import our opaque token
@Injectable()
export class TranslateService {
private _currentLang: string;
public get currentLang() {
return this._currentLang;
}
// inject our translations
constructor(@Inject(TRANSLATIONS) private _translations: any) {
}
public use(lang: string): void {
// set current language
this._currentLang = lang;
}
private translate(key: string): string {
// private perform translation
let translation = key;
if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
return this._translations[this.currentLang][key];
}
return translation;
}
public instant(key: string) {
// call translation
return this.translate(key);
}
}

설명

  • TRANSLATIONS 토큰을 가져 와서 생성자에 주입합니다.

Translate Pipe

이제 Translate Pipe를 만듭니다. Pipe는 간단합니다 - Pipe에 로직이 없습니다. 번역을 수행하기 위해 translate Service를 import하고 호출할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/translate/translate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '../translate'; // our translate service
@Pipe({
name: 'translate',
})
export class TranslatePipe implements PipeTransform {
constructor(private _translate: TranslateService) { }
transform(value: string, args: any[]): any {
if (!value) return;
return this._translate.instant(value);
}
}

번역 사용하기

Translate ServicePipe가 모두 완료되었습니다. App Component에서 사용해 봅시다.

App Module로 Import 하기

Translate ServicePipe를 사용하려면 응용 프로그램 모듈로 Import 해야합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TRANSLATION_PROVIDERS, TranslatePipe, TranslateService } from './translate';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, TranslatePipe ], // Inject Translate Pipe here
bootstrap: [ AppComponent ],
providers: [ TRANSLATION_PROVIDERS, TranslateService ]
})
export class AppModule { }

설명

  1. 응용프로그램 레벨에서 ServicePipe를 등록합니다.
  2. Translate Pipe를 global하게 (응용 프로그램 전체에서) 사용할 수 있도록합니다. 이 포스트를 참고하세요.

App Component

이제 Component에서 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { TranslateService } from './translate';
@Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
})
export class AppComponent implements OnInit {
public translatedText: string;
public supportedLanguages: any[];
constructor(private _translate: TranslateService) { }
ngOnInit() {
// standing data
this.supportedLangs = [
{ display: 'English', value: 'en' },
{ display: 'Español', value: 'es' },
{ display: '华语', value: 'zh' },
];
// set current langage
this.selectLang('es');
}
isCurrentLang(lang: string) {
// check if the selected lang is current lang
return lang === this._translate.currentLang;
}
selectLang(lang: string) {
// set current lang;
this._translate.use(lang);
this.refreshText();
}
refreshText() {
// refresh translation when language change
this.translatedText = this._translate.instant('hello world');
}
}

설명

  1. 지원되는 모든 언어를 저장하는 supportedLanguages 배열을 사용합니다.
  2. 기본적으로 언어는 스페인어로 설정됩니다. 물론 사용자의 브라우저 언어 navigator.language를 읽고 그에 따라 기본 언어를 설정할 수 있습니다.
  3. 새로운 언어가 선택되면 우리는 translatedText를 새로 고칩니다. (이어지는 내용에서 새로 고침을 처리하는 방법을 향상시킬 것입니다).

App HTML 뷰

다음은 HTML 뷰입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--app/app.component.html-->
<div class="container">
<h4>Translate: Hello World</h4>
<!--languages-->
<div class="btn-group">
<button *ngFor="let lang of supportedLangs" (click)="selectLang(lang.value)" class="btn btn-default" [class.btn-primary]="isCurrentLang(lang.value)">
{{ lang.display }}
</button>
</div>
<div>
<!--translate with pipe-->
<p>
Translate With Pipe: <strong>{{ 'hello world' | translate }}</strong>
</p>
<!--reanslate with service-->
<p>
Translate with Service: <strong>{{ translatedText }}</strong>
</p>
</div>
</div>

배럴(Barrel)로 묶기

배럴(barrel)로 Export 하기

translation 모듈을 배럴로 Export할 것입니다. 배럴은 여러 모듈의 Export를 단일 모듈로 감싸는 방법입니다. 배럴 자체는 다른 모듈의 선택된 Export를 다시 Export하는 모듈 파일입니다. 자세한 내용은 Angular 공식 문서를 참조하십시오.

1
2
3
4
5
// app/translate/index.ts
export * from './translate.service';
export * from './translations';
export * from './translate.pipe';

부트스트랩(Bootstrap)

이제 응용 프로그램을 부트스트랩 합니다.

1
2
3
4
5
6
7
8
// app/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

시스템 설정

systemjs를 사용하고 Angular CLI를 프로젝트 설정에 사용하는 경우 translate barrel을 system-config.ts 파일에 포함합니다.

1
2
3
4
5
6
7
// system.config.ts
// App specific barrels.
'app',
'app/translate', // include this line
'app/shared',
/** @cli-barrel */

거의 끝났습니다. 하지만..

애플리케이션을 실행해 봅시다. 응용 프로그램이 로드되면 스페인어가 선택되고 번역은 예상대로 hola mundo를 표시합니다.
영어를 선택해 보십시오. 하지만 Translate Pipe 값이 업데이트되지 않습니다.

왜 그럴까요?

Angular 2 Pipe는 기본적으로 Pure합니다. Angular는 입력값에 대한 순수한 변경을 감지한 경우에만 Pure Pipe를 실행합니다. 우리의 경우, 입력값이 변경되지 않아 여전히 Hello world입니다. 따라서 값이 업데이트되지 않습니다.

해결책

우리 Pipeimpure로 만듭니다. Angular는 모든 Component 변경 감지 사이클 동안 Impure Pipe를 실행합니다. 이 경우 언어 선택을 업데이트하면 변경 사항이 트리거되고 Impure Pipe가 그에 따라 업데이트됩니다.

우리 Pipe를 Impure로 만들려면 app/translate/translate.pipe.ts를 열고 다음 한줄을 추가하십시오.

1
2
3
4
5
6
7
8
9
// app/translate/translate.pipe.ts
...
@Pipe({
name: 'translate',
pure: false // add in this line, update value when we change language
})
...

Pipe에 대한 자세한 내용은 Angular 2 Pipes 문서를 참조하십시오.

Placeholder 사용을 위한 번역 정의 추가

먼저 번역 정의 파일에 번역을 추가해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
// lang-en.ts
export const LANG_EN_NAME = 'en';
export const LANG_EN_TRANS = {
'hello world': 'hello world',
'hello greet': 'Hello, %0 %1!', // two placeholder
'well done': 'Well done %0', // one placeholder
'good bye': 'bye bye', // only define in English
};
1
2
3
4
5
6
7
8
9
// lang-es.ts
export const LANG_ES_NAME = 'es';
export const LANG_ES_TRANS = {
'hello world': 'hola mundo',
'hello greet': 'Hola, %0 %1!',
'well done': '%0 bien hecho',
};
1
2
3
4
5
6
7
8
9
// lang-zh.ts
export const LANG_ZH_NAME = 'zh';
export const LANG_ZH_TRANS = {
'hello world': '你好,世界',
'hello greet': '你好, %0 %1!',
'well done': '干得好, %0',
};

번역에서 Placeholder 사용

이 작업은 꽤 쉽습니다. translate.service.tsinstant 함수를 수정하여 선택적 파라미터를 받아 들이도록 하면 됩니다. 파라미터가 문자열 또는 문자열 배열을 허용할 수 있도록 하면 더 좋습니다.

Translate Service 수정하기

1
2
3
4
5
6
7
8
9
10
11
// app/translate/translate.service.ts
...
public instant(key: string, words?: string | string[]) { // add optional parameter
const translation: string = this.translate(key);
if (!words) return translation;
return this.replace(translation, words); // call replace function
}
...

이제 replace 함수를 구현해야합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/translate/translate.service.ts
...
private PLACEHOLDER = '%'; // our placeholder
public replace(word: string = '', words: string | string[] = '') {
let translation: string = word;
const values: string[] = [].concat(words);
values.forEach((e, i) => {
translation = translation.replace(this.PLACEHOLDER.concat(<any>i), e);
});
return translation;
}
...

Translate Pipe 변경

Pipe도 업데이트 해야합니다.

1
2
3
4
5
6
7
8
// app/translate/translate.pipe.ts
...
transform(value: string, args: string | string[]): any { // args can be string or string array
if (!value) return;
return this._translate.instant(value, args); // pass in args
}
...

HTML 뷰에서 사용

이제 번역을 사용하기 위해 HTML 뷰를 업데이트 해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--app/app.component.html-->
<!--multiple value-->
<p>
Translate <strong class="text-muted">Hello, %0 %1!</strong>:
<br>
<strong>{{ 'hello greet' | translate:['Jane', 'Doe'] }}</strong>
</p>
<!--single value-->
<p>
Translate <strong class="text-muted">Well done %0</strong>:
<br>
<strong>{{ 'well done' | translate:'John' }}</strong>
</p>

언어 변경 Event를 Publish하고 Subscribe 하기

translate.service.ts 파일에 Event 발생기능을 추가합니다.

Translate Service 변경하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/translate/translate.service.ts
...
import { Injectable, Inject, EventEmitter } from '@angular/core'; // import event emitter
...
// add event
public onLangChanged: EventEmitter<string> = new EventEmitter<string>();
..
public use(lang: string): void {
...
this.onLangChanged.emit(lang); // publish changes
}
...

App Component에서 사용하기

이제 App Component에서 사용할 수 있습니다. 앞서 논의한 selectLang()에서 refreshText()를 제거하기 위한 코드를 리팩토링 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/app.component.ts
...
ngOnInit() {
// standing data
...
this.subscribeToLangChanged(); // subscribe to language changes
// set current language
this.selectLang('es');
}
selectLang(lang: string) {
// set default;
this._translate.use(lang);
// this.refreshText(); // remove this line
}
subscribeToLangChanged() {
// refresh text
// please unsubribe during destroy
return this._translate.onLangChanged.subscribe(x => this.refreshText());
}
...

이제 언어가 변경될 때마다 번역이 계속 업데이트됩니다.

기본언어 사용 및 대체언어 사용

새로운 속성을 추가하고 translate.service.tstranslate 함수를 리팩토링 합니다.

Translate Service 변경하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// app/translate/translate.service.ts
...
// add these 3 new properties
private _defaultLang: string;
private _currentLang: string;
private _fallback: boolean;
public get currentLang() {
return this._currentLang || this._defaultLang; // return default lang if no current lang
}
public setDefaultLang(lang: string) {
this._defaultLang = lang; // set default lang
}
public enableFallback(enable: boolean) {
this._fallback = enable; // enable or disable fallback language
}
private translate(key: string): string { // refactor our translate implementation
let translation = key;
// found in current language
if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
return this._translations[this.currentLang][key];
}
// fallback disabled
if (!this._fallback) {
return translation;
}
// found in default language
if (this._translations[this._defaultLang] && this._translations[this._defaultLang][key]) {
return this._translations[this._defaultLang][key];
}
// not found
return translation;
}
...

App Component에서 사용하기

ngOnInit()에서 기본언어와 대체언어를 설정할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/app.component.ts
...
ngOnInit() {
// standing data
...
this.subscribeToLangChanged();
// set language
this._translate.setDefaultLang('en'); // set English as default
this._translate.enableFallback(true); // enable fallback
// set current language
this.selectLang('es');
}
...

HTML 뷰에서 사용

HTML에서 테스트해 봅시다. 이전에 번역 정의 추가 단계에서 영어 번역을 위해 good bye를 추가했지만 스페인어와 중국어로 추가하지 않았습니다. 사용자가 스페인어 또는 중국어를 선택한 경우 디스플레이는 bye bye입니다. 왜냐하면 우리는 컴포너트에서 대체 언어 사용을 설정했기 때문입니다.

1
2
3
4
5
<p>
Translate <strong class="text-muted">Good bye (fallback)</strong>:
<br>
<strong>{{ 'good bye' | translate }}</strong>
</p>

요약

이게 전부입니다. ServicePipe와 대체언어, Event Publish/Subscribe 및 기본 언어와 같은 고급 기능을 사용하여 기본적인 번역 설정을 설명했습니다.


이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://scotch.io/tutorials/simple-language-translation-in-angular-2-part-1, https://scotch.io/tutorials/simple-language-translation-in-angular-2-part-2]

참고

공유하기