NG客制項目下的I18n國際化標準方案

方案選擇

國際化i18n

? 這個方案是最成熟的,同時也是官方的方案,但是這樣一個標準化的方案同時意味著靈活度不夠。當需要劃分feature module,需要客制化組件的時候,這個方案的實施的成本就會遠遠超過預期,因此在項目中放棄了該方案。

ngx-translate

? 這個方案是目前i18n一個比較優秀的替代方案,由Angular Core Team的成員Olivier Combe開發,可以看做另一個維度的i18n,除了使用Json替代xlf外,可以自定義provider也是這個方案的特色之一,最終選擇了該方案。

I18nSelectPipe & I18nPluralPipe

? 作為官方方案,這2個pipe在項目中仍然有機會被用到,特別是處理從API傳入數據時,使用這2個pipe會更便捷。

依賴安裝

github

https://github.com/ngx-translate/core

@ngx-translate/core

? 首先安裝npm包。

> npm install @ngx-translate/core --save
復制代碼

? 如果是NG4則需要指定版本為7.2.2。

引用ngx-translate

在app.module.ts中,我們進行引入,并加載。

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {TranslateModule} from '@ngx-translate/core';@NgModule({imports: [BrowserModule,TranslateModule.forRoot()],bootstrap: [AppComponent]
})
export class AppModule { }
復制代碼

? 請不要遺漏forRoot(),全局有且僅有一個forRoot會生效,所以你的feature module在加載TranslateModule時請用這個方法。

@NgModule({exports: [CommonModule,TranslateModule]
})
export class FeatureModule { }
復制代碼

? 如果你的featureModule是需要被異步加載的那么你可以用forChild()來聲明,同時不要忘記設置isolate。

@NgModule({imports: [TranslateModule.forChild({loader: {provide: TranslateLoader, useClass: CustomLoader},compiler: {provide: TranslateCompiler, useClass: CustomCompiler},parser: {provide: TranslateParser, useClass: CustomParser},missingTranslationHandler: {provide: MissingTranslationHandler, useClass: CustomHandler},isolate: true})]
})
export class LazyLoadedModule { }
復制代碼

? 其中有些內容是允許我們自己來定義加載,稍后進行描述。

異步加載Json配置文件

安裝http-loader

? ngx-translate為我們準備了一個異步獲取配置的loader,可以直接安裝這個loader,方便使用。

> npm install @ngx-translate/http-loader --save
復制代碼

使用http-loader

? 使用這個加載器還是很輕松愉快的,按照示例做就可以了。

export function HttpLoaderFactory(http: HttpClient) {return new TranslateHttpLoader(http);
}TranslateModule.forRoot({loader: {provide: TranslateLoader,useFactory: HttpLoaderFactory,deps: [HttpClient]}})
復制代碼

? 如果要做AOT,只要稍微修改一下Factory就可以了。

export function createTranslateLoader(http: HttpClient) {return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
復制代碼

i18n Json文件

? 先建立一個en.json。

{"HELLO": "hello {{value}}"
}
復制代碼

? 再建立一個cn.json。

{"HELLO": "歡迎 {{value}}"
}
復制代碼

? 2個文件都定義了HELLO這個key,當i18n進行處理的時候,會獲取到對應的值。

? 將這2個文件放到服務器端的/assets/i18n/目錄下,就快要通過http-loader異步獲取到了。

Component中的使用

import {Component} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';@Component({selector: 'app',template: `<div>{{ 'HELLO' | translate:param }}</div>`
})
export class AppComponent {param = {value: 'world'};constructor(translate: TranslateService) {// this language will be used as a fallback when a translation isn't found in the current languagetranslate.setDefaultLang('en');// the lang to use, if the lang isn't available, it will use the current loader to get themtranslate.use('en');}
}
復制代碼

? template中使用了HELLO這個key,并且通過translatePipe來進行處理,其中的param,使得I18n中的value會被解析成world。

? 而在constructor中依賴的TranslateService,是我們用來對i18n進行設置的provider,具體的Methods可以參照官方文檔。

根據模組來拆分I18n

? 以上內容都不是重點,如果簡單使用統一的json,很難滿足復雜的開發需求。我們需要更靈活的方案來解決開發中的痛點,這一點ngx-translate也為我們準備了改造的方法。

i18n文件跟隨模組和組件

? 項目的模組和組件隨著項目開發會逐漸增多,統一維護會耗費不少精力,因此選擇使用ts來描述I18n內容,同時在模組中引入。當然,如果有使用json-loader,也可以使用json,文件修改為en.ts。

export const langPack = {"Workspace@Dashboard@Hello": "hello {{value}}"
}
復制代碼

? 在組件中將i18n內容合并成組件的langPack,這樣,每個組件只要維護各自的langPack即可,不需要再過多的關注其他部分的i18n。

import {langPack as cn} from './cn';
import {langPack as en} from './en';export const langPack = {en,cn,
}
復制代碼

命名規則與合并

? 國際化比較容易碰到的一個問題是,各自維護各自的key,如果出現重名的時候就會出現相互覆蓋或錯誤引用的問題,因此我們需要定義一個命名規則,來防止串號。目前沒有出現需要根據版本不同修改i18n的需求,因此以如下方式定義key。

Project@Feature@Tag
復制代碼

? 各組件的i18n最終會匯總在module中,因此會通過如下方式進行合并。

import {DashboardLangPack} from './dashboard'
export const WorkspaceLangPack = {en: {...DashboardLangPack.en},cn: {...DashboardLangPack.cn}}
復制代碼

? 各module在DI的過程中也會通過類似的方式進行合并,最終在app module形成一個i18n的匯總,并通過自定義的loader來進行加載。

自定義實施

CustomLoader

? 想要定義CustomLoader,首先我們需要加載TranslateLoader。

import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
復制代碼

? 然后我們自定義一個CustomLoader。

export class CustomLoader implements TranslateLoader {langPack = {};constructor(langPack) {this.langPack = langPack;}getTranslation(lang: string): Observable<any> {console.log(this.langPack[lang]);return Observable.of(this.langPack[lang]);}}
復制代碼

? 這樣一個簡單的CustomLoader,就可以滿足我們對于同步加載i18n的需求,可以看到,我們定義了一個Observable的方法getTranslation,通過這個方法,我們返回了一個數據管道。我們看一下TranslateLoader的聲明。

export declare abstract class TranslateLoader {abstract getTranslation(lang: string): Observable<any>;
}
復制代碼

? 在ngx-translate使用我們的loader時,會使用getTranslation方法,所以Loader的關鍵就在于正確的定義getTranslation的數據獲取部分。

? 我們再來看一下之前有提到過的TranslateHttpLoader,在定義了getTranslation的同時,從constructor里獲取了HttpClient。

export declare class TranslateHttpLoader implements TranslateLoader {private http;prefix: string;suffix: string;constructor(http: HttpClient, prefix?: string, suffix?: string);/*** Gets the translations from the server* @param lang* @returns {any}*/getTranslation(lang: string): any;
}復制代碼

? 至此,Loader如何實現已經很清晰了,我們看一下調用的方式。

      TranslateModule.forRoot({loader: {provide: TranslateLoader,useFactory: () => new CustomLoader(option.langPack)}})
復制代碼

? loader的用法大致與ng的provider相當,這里因為要傳值,使用了useFactory,同樣也有useClass和deps,可以參考ng的相關用法。

? 當loader被正確配置后,i18n的基礎工作就能被完成了,loader的作用就是為ngx-translate來獲取i18n的字典,然后通過當前的lang來切換字典。

CustomHandler

? i18n由于要維護多語種字典,有時會發生內容缺失的情況,當這個時候,我們需要安排錯誤的處理機制。

? 第一種方式,我們可以使用useDefaultLang,這個配置的默認為true,因此我們需要設置默認配置,需要加載TranslateService,并保證默認語言包的完整。

import { TranslateService } from '@ngx-translate/core';class CoreModule {constructor(translate: TranslateService) {translate.setDefaultLang('en');}}
復制代碼

? 另一種方式,是我們對缺少的情況進行Handler處理,在這個情況下,我們需要預先編寫CustomLoader。

import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';export class CustomHandler implements MissingTranslationHandler {handle(params: MissingTranslationHandlerParams) {return 'no value';}
}
復制代碼

? 我們還是來看一下Handler的相關聲明。

export interface MissingTranslationHandlerParams {/*** the key that's missing in translation files** @type {string}*/key: string;/*** an instance of the service that was unable to translate the key.** @type {TranslateService}*/translateService: TranslateService;/*** interpolation params that were passed along for translating the given key.** @type {Object}*/interpolateParams?: Object;
}
export declare abstract class MissingTranslationHandler {/*** A function that handles missing translations.** @abstract* @param {MissingTranslationHandlerParams} params context for resolving a missing translation* @returns {any} a value or an observable* If it returns a value, then this value is used.* If it return an observable, the value returned by this observable will be used (except if the method was "instant").* If it doesn't return then the key will be used as a value*/abstract handle(params: MissingTranslationHandlerParams): any;
}
復制代碼

? 我們能很容易的了解到,當ngx-translate發現錯誤時,會通過handle丟一個MissingTranslationHandlerParams給我們,而后我們可以根據這個params來安排錯誤處理機制。

? 在這里我們簡單的返回了“no value”來描述丟失數據,再來加載這個handle。

      TranslateModule.forRoot({missingTranslationHandler: { provide: CustomHandler, useClass: MyMissingTranslationHandler },useDefaultLang: false})
復制代碼

? 想要missingTranslationHandler生效,不要忘記useDefaultLang!!!

CustomParser

? 這個provider需要添加@Injectable裝飾器,還是先給出code。

import { Injectable } from '@angular/core';
import { TranslateParser, TranslateDefaultParser } from '@ngx-translate/core';@Injectable()
export class CustomParser extends TranslateDefaultParser {public interpolate(expr: string | Function, params?: any): string {console.group('interpolate');console.log('expr');console.log(expr);console.log('params');console.log(params);console.log('super.interpolate(expr, params)');console.log(super.interpolate(expr, params));console.groupEnd()const result: string = super.interpolate(expr, params)return result;}getValue(target: any, key: string): any {const keys = super.getValue(target, key);console.group('getValue');console.log('target');console.log(target);console.log('key');console.log(key);console.log('super.getValue(target, key)');console.log(super.getValue(target, key));console.groupEnd()return keys;}
}
復制代碼

? 顧名思義Parse負責ngx-translate的解析,getValue進行解析,interpolate替換變量。看一下聲明的部分,注釋得相當清晰了。

export declare abstract class TranslateParser {/*** Interpolates a string to replace parameters* "This is a {{ key }}" ==> "This is a value", with params = { key: "value" }* @param expr* @param params* @returns {string}*/abstract interpolate(expr: string | Function, params?: any): string;/*** Gets a value from an object by composed key* parser.getValue({ key1: { keyA: 'valueI' }}, 'key1.keyA') ==> 'valueI'* @param target* @param key* @returns {string}*/abstract getValue(target: any, key: string): any;
}
export declare class TranslateDefaultParser extends TranslateParser {templateMatcher: RegExp;interpolate(expr: string | Function, params?: any): string;getValue(target: any, key: string): any;private interpolateFunction(fn, params?);private interpolateString(expr, params?);
}復制代碼

? 我的示例代碼中只是簡單的將過程給打印了出來,在實際操作中,Parse可以對數據進行相當程度的操作,包括單復數和一些特別處理,我們應該在這個provider中去進行定義,可以考慮通過curry(柯里化)的純函數疊加一系列處理功能。

? 引用也是同樣的簡單。

      TranslateModule.forRoot({parser: { provide: TranslateParser, useClass: CustomParser },}),
復制代碼

CustomCompiler

? 這個provider也需要添加@Injectable裝飾器,先看一下代碼。

@Injectable()
export class CustomCompiler extends TranslateCompiler {compile(value: string, lang: string): string | Function {console.group('compile');console.log('value');console.log(value);console.log('lang');console.log(lang);console.groupEnd()return value;}compileTranslations(translations: any, lang: string): any {console.group('compileTranslations');console.log('translations');console.log(translations);console.log('lang');console.log(lang);console.groupEnd()return translations;}
}
復制代碼

? 在運行過程中,我們會發現compileTranslations被正常觸發了,而compile并未被觸發。并且通過translate.use()方式更新lang的時候compileTranslations只會觸發一次,Parse會多次觸發,因此可以判定translations加載后lang會被緩存。先看一下聲明。

export declare abstract class TranslateCompiler {abstract compile(value: string, lang: string): string | Function;abstract compileTranslations(translations: any, lang: string): any;
}
/*** This compiler is just a placeholder that does nothing, in case you don't need a compiler at all*/
export declare class TranslateFakeCompiler extends TranslateCompiler {compile(value: string, lang: string): string | Function;compileTranslations(translations: any, lang: string): any;
}復制代碼

? 然后看一下官方的描述。

How to use a compiler to preprocess translation values

By default, translation values are added "as-is". You can configure a compiler that implements TranslateCompiler to pre-process translation values when they are added (either manually or by a loader). A compiler has the following methods:

  • compile(value: string, lang: string): string | Function: Compiles a string to a function or another string.
  • compileTranslations(translations: any, lang: string): any: Compiles a (possibly nested) object of translation values to a structurally identical object of compiled translation values.

Using a compiler opens the door for powerful pre-processing of translation values. As long as the compiler outputs a compatible interpolation string or an interpolation function, arbitrary input syntax can be supported.

? 大部分時候我們不會用到compiler,當我們需要預處理翻譯值的時候,你會感受到這個設計的強大之處。

TranslateService

? 單獨列出這個service是因為你一定會用到它,而且它真的很有用。

Methods:

  • setDefaultLang(lang: string): 設置默認語言
  • getDefaultLang(): string: 獲取默認語言
  • use(lang: string): Observable<any>: 設置當前使用語言
  • getTranslation(lang: string): Observable<any>:獲取語言的Observable對象
  • setTranslation(lang: string, translations: Object, shouldMerge: boolean = false): 為語言設置一個對象
  • addLangs(langs: Array<string>): 添加新的語言到語言列表
  • getLangs(): 獲取語言列表,會根據default和use的使用情況發生變化
  • get(key: string|Array<string>, interpolateParams?: Object): Observable<string|Object>: 根據key獲得了一個ScalarObservable對象
  • stream(key: string|Array<string>, interpolateParams?: Object): Observable<string|Object>: 根據key返回一個Observable對象,有翻譯值返回翻譯值,沒翻譯值返回key,lang變更也會返回相應內容。
  • instant(key: string|Array<string>, interpolateParams?: Object): string|Object: 根據key返回相應內容,注意這是個同步的方法,如果不能確認是不是應該使用,請用get。
  • set(key: string, value: string, lang?: string): 根據key設置翻譯值
  • reloadLang(lang: string): Observable<string|Object>: 重新加載語言
  • resetLang(lang: string): 重置語言
  • getBrowserLang(): string | undefined: 獲得瀏覽器語言(比如zh)
  • getBrowserCultureLang(): string | undefined: 獲得瀏覽器語言(標準,比如zh-CN)

API、state的i18n處理方案

? ngx-translate已經足夠強大,但我們仍需要拾遺補缺,在我們獲取數據的時候對某些需要i18n的內容進行處理,這個時候我們可以使用I18nSelectPipe和I18nPluralPipe。

? 具體的使用方法在官網已有明確的描述,可以參考具體的使用方式。

? https://angular.cn/api/common/I18nSelectPipe

? https://angular.cn/api/common/I18nPluralPipe

I18nSelectPipe

? 這里以I18nSelectPipe的使用進行簡單的描述,I18nPluralPipe大致相同。

? 如果數據在傳入時或根節點就已經區分了語言,那么我們其實不需要使用pipe,就可以直接使用了。pipe會使用的情況大致是當我們遇到如下數據結構時,我們會期望進行自動處理。

  data = {'cn': '中文管道','en': 'English Pipe','other': 'no value'}
復制代碼

? 其中other是當語言包沒有正確命中時顯示的內容,正常的數據處理時其實不會有這部分內容,當未命中時,pipe會處理為不顯示,如果有需要添加other,建議使用自定義pipe來封裝這個操作。

? 設置當前lang。

  lang = 'en';
復制代碼

? 當然,如果你還記得之前我們介紹過的TranslateService,它有一個屬性叫currentLang,可以通過這個屬性獲取當前的語言,若是希望更換語言的時候就會同步更換,還可以使用onLangChange。

this.lang = this.translate.currentLang;
//or
this.translate.onLangChange.subscribe((params: LangChangeEvent) => {this.lang = params.lang;
});
復制代碼

? 最后,我們在Component里加上pipe,這個工作就完成了

<div>{{lang | i18nSelect: data}} </div>
復制代碼

總結

? i18n的方案其實更多是基于項目來進行選擇的,某一項目下合適的方案,換到其他項目下可能就會變得不可控制。而項目的復雜度也會對i18n的進行產生影響,所以盡可能的,在項目早期把i18n的方案落實下去,調整之后的策略去匹配i18n方案。

轉載于:https://juejin.im/post/5a716902518825733201ee4c

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/452773.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/452773.shtml
英文地址,請注明出處:http://en.pswp.cn/news/452773.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Flsak項目--圖片驗證碼

0. 圖片驗證碼的使用流程 2.后端接口編寫 verify_code.py中編寫接口代碼&#xff1a; # coding:utf-8from . import api from ihome.utils.captcha.captcha import captcha from ihome import redis_store, constants, db from flask import current_app, jsonify, make_respo…

數據庫與數據庫管理系統

數據庫是長期存儲在計算機內有組織的大量的共享的數據集合。可以供各種用戶共享&#xff0c;具有最小冗余度和較高的數據獨立性。數據庫管理系統在數據庫建立、運用和維護時對數據庫進行統一控制&#xff0c;以保證數據的完整性、安全性&#xff0c;并在多用戶同時使用數據庫時…

如何提高團隊情商

在公司發展中&#xff0c;總裁&#xff0c;總監&#xff0c;經理&#xff0c;項目經理&#xff0c;他們對團隊的建設意義重大&#xff0c;工作很重要&#xff0c;但團隊的情商才更重要&#xff0c;筆者公司的一個團隊&#xff0c;三十多個人就像一個人&#xff0c;命令所到之處…

ubuntu java classpath 設置_在Ubuntu中正確設置java classpath和java_home

我有錯誤Exception in thread"main" java.lang.NoClassDefFoundError:當我嘗試在Ubuntu上運行編譯類時。我使用的是一個非常簡單的helloworld示例&#xff0c;互聯網上已有數百萬的響應表明我的classpath和java_home變量設置錯誤。但是&#xff0c;我已經將etc/envir…

Polo the Penguin and Matrix

Little penguin Polo has an n??m matrix, consisting of integers. Lets index the matrix rows from 1 to n from top to bottom and lets index the columns from 1 to m from left to right. Lets represent the matrix element on the intersection of row i and column…

趣解 XSS和CSRF的原理

參考文章&#xff1a;趣解 XSS和CSRF的原理 推薦網站&#xff1a;古黑論 感謝作者分享&#xff01;

js異步解決方案 --- 回調函數 vs promise vs generater/yield vs async/await

javascript -- 深度解析異步解決方案 高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設計理念, js天生為異步而生, 正如布道者樸靈在 node深入淺出--(有興趣的可以讀一下, 很有意思^_^) , 異步很早就存在于操作系統的底層, 意外的是&#xff0c;在絕大多數高級編程語言中…

什么是TPDU

TPDU,全稱Transport Protocol Data Unit&#xff0c;是指傳送協議數據單元。代表從一個傳輸實體發送至另一個傳輸實體的消息。 我們需要為傳輸實體之間交換的數據單元起一個更加一般化的名字&#xff0c;TCP的術語是數據段&#xff0c;它很容易混淆&#xff0c;而且在TCP領域之…

sql注入基本原理

1. 參考文獻&#xff1a; 趣解SQL注入原理 Sql注入基本原理 2.參考書籍

項目管理雜談-員工的積極性在哪里?

項目開發過程中&#xff0c;每每有人感嘆&#xff0c;曾幾何時&#xff0c;隊伍如何好帶&#xff0c;如何好用&#xff0c;而如今&#xff0c;人心繁雜&#xff0c;隊伍不好帶了。很多人的想法是“人望高處走”&#xff0c;不停的尋找待遇及其他方面更好的單位。其實&#xff0…

centos7硬盤分區

首先在虛擬機的設置中為系統添加硬盤 使用fdisk -l /dev/sdb 查看未分區的硬盤 fdisk -l /dev/sda 這是已經分區好得 接下來我們就要對sdb進行分區: 首先使用fdisk /dev/sdb 接著輸入m可以看到詳細命令 進行添加分區 已經建立好4個主分區&#xff0c;在建立時會看到以下 刪除…

java上傳rar文件_java實現上傳zip/rar壓縮文件,自動解壓

在pom中添加解壓jar依賴4.0.0org.springframework.bootspring-boot-starter-parent2.1.2.RELEASEcom.hfuncompress0.0.1-SNAPSHOTuncompress上傳壓縮文件(rar或者zip格式),解壓1.8org.springframework.bootspring-boot-starter-weborg.projectlomboklomboktrueorg.springframew…

從MapReduce的執行來看如何優化MaxCompute(原ODPS) SQL

摘要&#xff1a; SQL基礎有這些操作&#xff08;按照執行順序來排列&#xff09;&#xff1a; from join(left join, right join, inner join, outer join ,semi join) where group by select sum distinct count order by 如果我們能理解mapreduce是怎么實現這些SQL中的基本操…

套接字(socket)基本知識與工作原理

套接字&#xff08;socket&#xff09;基本知識與工作原理 一、Socket相關概念 Socket通常也稱作“套接字”&#xff0c;用于描述IP地址和端口&#xff0c;是一個通信鏈的句柄。&#xff08;其實就是兩個程序通信用的。&#xff09; SOCKET用于在兩個基于TCP/IP協議的應用程序之…

python 多線程--重點知識

1.全局變量global的用法 2.多線程共享全局變量-args參數 注意args參數類型為元組&#xff0c;逗號不能少&#xff01;

Flask WTForm表單的使用

運行環境&#xff1a; python2.7 flask 0.11 flask-wtf 0.14.2 wtform能夠通過一個類定義一些字段&#xff0c;這些字段會在前端生成標簽&#xff0c;并且通過設置字段的驗證規則&#xff0c;自動判斷前端輸入數據的格式。 一般用于用戶登錄&#xff0c;用戶注冊等信息錄入。…

Java與C#個人之比較

網上這方面的比較文章已經有不少了&#xff0c;不過大都是要么從很高的角度說的&#xff0c;要么就是從底層說的&#xff0c;本人就以自己這幾年的編程經歷中的感受&#xff0c;來談談自己的體會。 相似性&#xff1a; Java和C#都是一門面向對象的語言&#xff0c;Java更多地…

java利用子類求正方形_Java程序設計實驗2011

(2)掌握對象的聲明和使用&#xff1b;(3)掌握構造方法的概念和使用&#xff1b;(4)掌握類及成員的訪問控制符。2、實驗任務(1)閱讀下面的程序&#xff0c;在main()方法里添加語句完成如下的功能&#xff1a;①創建一個MyV alue類的對象myV alue。②為myV alue對象中的value域賦…

當導用模塊與包的import與from的問題(模塊與包的調用)

當在views.py里寫impor models會不會報錯呢&#xff1f; 1、Python里面的py文件都是每一行的代碼。2、Python解釋器去找一個模塊的時候&#xff0c;只去sys.path的路徑里找3、django項目啟動&#xff08;django項目的啟動文件是manage.py&#xff09;啟動項目是將manage.py的路…

ack和seq

ACK (Acknowledgement&#xff09;&#xff0c;即確認字符&#xff0c;在數據通信中&#xff0c;接收站發給發送站的一種傳輸類控制字符。表示發來的數據已確認接收無誤。 seq是序列號&#xff0c;這是為了連接以后傳送數據用的&#xff0c;ack是對收到的數據包的確認&#xff…