國際化 (i18n) 是一個過程,用於對你的應用進行設計和準備以便在全球不同地區使用。本地化是指為不同的本地環境建構應用版本的過程,包括提取用於翻譯成不同語言的文字,以及格式化特定本地環境的資料。
本地環境(locale)用於標識某個區域(例如某個國家/地區),人們會在該區域內使用特定的語言或語言變體。本地環境決定了日期、時間、數字和貨幣的格式和解析方式,以及各種測量單位和時區、語言、國家/地區的翻譯名稱。
getLangMenuItems(data) {
this.menuItemsLang = data;
NbMenuItems.forEach((item) => {
// home.menuitem recursive of menu structure
item.title = this.languageService.recursiveLangAppend(data, item);
});
this.items = NbMenuItems;
}
<nb-card-header> {{ "ROBOT.SETTING" | translate }} </nb-card-header>
npm install @ngx-translate/core --save
npm install @ngx-translate/http-loader --save
src\app\app.module.ts
// Ngx-Translate
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
...
// 在根模組中 **(預設會使用「/assets/i18n/」及「.json」)**
export `function createTranslateLoader`(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
...
imports: [
...
// 引入 TranslateModule
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient],
},
})
]
})
export class AppModule {}
安裝 @ngx-translate
並設定完成後,我們來實際應用多國語系模組到 my-app
中。
src\assets\i18n\zh-TW.json
{
...
"MENU_ITEMS": {
...
"ABOUT": {
"TITLE": "公司簡介"
},
"TENANT": {
"TITLE": "租戶管理"
},
"LAND": {
"TITLE": "土地管理"
}
}
}
src\assets\i18n\zh-US.json
{
...
"MENU_ITEMS": {
...
"ABOUT": {
"TITLE": "ABOUT"
},
"TENANT": {
"TITLE": "TENANT"
},
"LAND": {
"TITLE": "LAND"
}
}
}
src\app\core\utils\language.service.ts
import { Injectable } from '@angular/core';
import { ReplaySubject } from 'rxjs';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
/**
* ### Language Service - 監聽語系改變或解析需要翻譯的目錄結構服務
*
* @export
* @class LanguageService
*/
@Injectable({
providedIn: 'root',
})
export class LanguageService {
constructor(private translateService: TranslateService) {}
/**
* ### ReplaySubject - language$
*
* @memberof LanguageService
*/
language$ = new ReplaySubject<LangChangeEvent>(1);
/**
* ### langService
*
* @type {*}
* @memberof LanguageService
*/
langService: any = null;
/**
* set i18n language
*
* @param {string} [language='']
* @memberof LanguageService
*/
setLang(language: string = '') {
let lang = language || localStorage.getItem('lang') || navigator.language;
localStorage.setItem('lang', lang);
// 觸發 Rxjs
this.setLangRxjs(lang);
}
/**
* set language RxJS
*
* @param {string} [lang='']
* @memberof LanguageService
*/
setLangRxjs(lang: string = '') {
this.langService = this.translateService.onLangChange.pipe(take(1)).subscribe((result) => {
this.language$.next(result);
});
// 改變使用的語系
this.translateService.use(lang);
}
/**
* ### 解析巢狀結構目錄
*
* i18n recursive of menu structure
*
* @param {object} langData
* @param {object} item
* @return {string} item.title
* @memberof LanguageService
*/
recursiveLangAppend(langData: any, item: any) {
if (!langData || !item) return '';
if (!!item.children) {
item.children.forEach((element: any) => {
return this.recursiveLangAppend(langData, element);
});
}
if (item.key.split('.').length > 1) {
let childTitleObj: any = {};
let childTitle = '';
item.key.split('.').forEach((val: any) => {
if (!!Object.keys(childTitleObj).length) {
childTitle = childTitleObj[val];
childTitleObj = childTitleObj[val];
} else childTitleObj = langData[val];
});
item.title = childTitle;
} else item.title = langData[item.key];
return item.title;
}
}
src\app\home\home.component.ts
// @ngx-translate
import { TranslateService } from '@ngx-translate/core';
// Language Service - 監聽語系改變或解析需要翻譯的目錄結構
import { LanguageService } from '@core/utils/language.service';
/**
* ## 首頁元件
*
* @export
* @class HomeComponent
* @implements {OnInit}
*/
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
// 注入服務
constructor(
private translateService: TranslateService,
private languageService: LanguageService
) {
// 取得瀏覽器目前語系
this.translateService.use(localStorage.getItem('lang') || navigator.language);
// 將對應語系寫入語系菜單中
this.translateService.get('HEADER.LANG').subscribe((resp) => this.getLang(resp));
}
/**
* ### 側邊巢狀菜單
*
* @type {NbMenuItem[]}
* @memberof HomeComponent
*/
items: NbMenuItem[] = NbMenuItems;
/**
* ### 語系項目
*
* @memberof HomeComponent
*/
languageMenu = [{ title: '', tag: '' }];
/**
* ### 可供切換的語系菜單的 i18n
*
* @param {*} data
* @memberof HomeComponent
*/
getLang(data: any) {
this.languageMenu = [
{ title: data.TW, tag: 'zh-TW' },
{ title: data.US, tag: 'en-US' },
];
}
/**
* ### 縮放側邊巢狀菜單
*
* @return {*}
* @memberof HomeComponent
*/
toggle() {
this.sidebarService.toggle(true);
return false;
}
/**
* ### i18n 翻譯側邊巢狀菜單
*
* @param {*} data
* @memberof HomeComponent
*/
getNbMenuItems(data: any) {
NbMenuItems.forEach((item: any) => {
item.title = this.languageService.recursiveLangAppend(data, item);
});
}
ngOnInit(): void {
this.nbMenuService.onItemClick().subscribe((title: { item: any; tag: any }) => {
// 監聽到有切換語系的動作且為合法語系則進入更換語言的方法
if (title.item.tag === 'zh-TW' || title.item.tag === 'en-US') {
this.languageService.setLang(title.item.tag);
this.translateService.get('HEADER.LANG').subscribe((resp) => this.getLang(resp));
this.translateService.get('MENU_ITEMS').subscribe((resp) => this.getNbMenuItems(resp));
}
});
}
}
src\app\home\home.menuitem.ts
export const NbMenuItems = [
{
key: 'ABOUT.TITLE', // 對應 i18n [json檔] 的 key
title: '公司簡介',
icon: { icon: 'at-outline', pack: 'eva' },
link: '/home/about',
},
{
key: 'TENANT.TITLE', // 對應 i18n [json檔] 的 key
title: '租戶管理',
icon: { icon: 'briefcase-outline', pack: 'eva' },
link: '/home/tenant',
},
{
key: 'LAND.TITLE', // 對應 i18n [json檔] 的 key
title: '土地管理',
icon: { icon: 'bar-chart-2-outline', pack: 'eva' },
link: '/home/land',
},
];
src\app\home\home.component.html
<nb-layout class="home">
<!-- header -->
<nb-layout-header fixed>
<div class="header">
<a (click)="toggle()">
<!-- 縮放側邊巢狀菜單 -->
<nb-action icon="menu-outline"></nb-action>
</a>
<a>
<!-- 可供切換的語言項目 -->
<nb-action icon="globe-outline" [nbContextMenu]="languageMenu"></nb-action>
</a>
</div>
</nb-layout-header>
<!-- 側邊巢狀菜單 -->
<nb-sidebar>
<nb-menu [items]="items"></nb-menu>
</nb-sidebar>
<!-- 頁面內容 -->
<nb-layout-column class="colored-column-info">
<router-outlet></router-outlet>
</nb-layout-column>
</nb-layout>
src\app\home\home.component.scss
.home {
.header {
width: 100%;
display: flex;
justify-content: space-between;
}
}
黃色區塊
為延遲加載的 i18n檔案,藍色為預設檔案,
此作法可將各模組的 i18n分開管理,與延遲載入模組的檔案結構相同並獲得延遲載入 i18n 的效果。
setLangRxjs(lang: string = '') {
this.langService = this.translateService.onLangChange.pipe(take(1)).subscribe((result) => {
this.language$.next(result);
});
this.translateService.use(lang);
}
{
"DASHBOARD": {
"PIE": "圓餅圖",
"BAR": "長條圖",
"HEAT": "熱力圖",
"CHART_QUARY": {
"LONGBAR": "很長的",
"HOT": "很熱的"
}
}
}
import { TranslateService } from '@ngx-translate/core';
import { LanguageService } from '@core/utils/language.service';
constructor(private translateService: TranslateService, private languageService: LanguageService) {
this.languageService.language$.subscribe((resp) => {
this.translateService.use(resp.lang);
this.translateService.get('DASHBOARD').subscribe((lang) => (this.DASHBOARD = lang));
});
}
{
"DASHBOARD": {
"PIE": "{{itemNo}} 圓餅圖",
"BAR": "{{itemStr}} 長條圖",
"HEAT": "{{itemStr}} 熱力圖",
"CHART_QUARY": {
"LONGBAR": "很長的",
"HOT": "很熱的"
}
}
}
{{ "DASHBOARD.PIE" | translate: { itemNo: "1號" } }}
{{ "DASHBOARD.BAR" | translate: { itemStr: "DASHBOARD.CHART_QUARY.LONGBAR" | translate } }}
Angular 本身有提供多國語系 Angular 國際化 的功能,但是不同語系需要編譯不同版本,使用上較為複雜,因此較推薦使用 ngx-translate
,即使在開發模式下也能方便看到即時切換語系的效果。
又有新的需求了,為了加強租賃物件本身的宣傳,需要新的元件讓使用者維護物件資訊。