角度:ngx-translateライフハック。 TranslateCompiler

良い一日。







近い将来、ngx-translateライフハックを公開する予定です。









 <p>This entity created at 15:47 01-02-2003 by Some Guy</p>
      
      





この行のen.jsonに新しいフィールドを追加します。







 "ENTITY_CREATED_AT": "This entity created at {{createdAtDate}} by {{guyName}}"
      
      





コンポーネントのビューでは、当然、translateディレクティブまたはpipeを追加します







 <div translate="ENTITY_CREATED_AT" [translateParams]="{createdAtDate: entity.createdAt, guyName: entity.name}"></div>
      
      





しかし、このアプローチでは問題が発生します-createdAtDateフィールドは既にローカライズされている必要があり、そうでない場合はnew Date().toString().



の結果が表示されnew Date().toString().









この問題には2aの解決策があります。







いくつかのベストプラクティス!

私はtranslateParamsの別のオブジェクトを転送して、jsonのフィールドを実際のモデルのフィールドに配置することを好みます。

トビッシュ:







 //our Entity model class Entity { createdAt: Date; name: string; someComplexField: ComplexType; } //inside some HTML [translateParams]="{createdAtDate: entity.createdAt, guyName: entity.name}" // separate object
      
      





もちろん、jsonでは、エンティティモデルでフィールド名を明示的に指定することで、実際のモデルのフィールドを参照することができました。







 "ENTITY_CREATED_AT": "This entity created at {{createdAt}} by {{name}}" //  entity.createdAt, entity.name
      
      





translateParamsでは、単にエンティティを渡します。







 <div translate="ENTITY_CREATED_AT" [translateParams]="entity"></div>
      
      





しかし、すでに10の言語をサポートしており、モデルがngx-translateに関連付けられているフィールドの1つの名前を突然変更した場合はどうなりますか?







ビューコンポーネント内のヘルプ機能。


ビューコンポーネント内に関数を作成し、translateParamsに転送するための既製のオブジェクトを返します。







 public getEntityTranslateParam(dateFormat: string) { return { createdAtDate: moment.format(dateFormat, entity.createdAt), //      guyName: entity.name }; }
      
      





欠点があります-そのような機能で各コンポーネントを詰まらせる必要があります。 もちろん、このようなオブジェクトを作成する特定のサービスを追加して、コンポーネントのコードを少しきれいにすることができます。このサービスはどこにでも注入する必要があるため、オブジェクトが十分に大きい場合にのみ意味があります。







ENTITY_CREATED_ATを2つの部分に分けます。


 "ENTITY_CREATED_AT_1": "This entity created at", "ENTITY_CREATED_AT_2": "by {{guyName}}"
      
      





ここで私たちは成長し、HTMLを読むことが難しくなっていますが、今ではあらゆる種類のパイプを使用できます! (もちろん、パイプロケーションディレクティブを使用できますが、div内にさらにhtmlを作成する必要があります)







 <div> {{'ENTITY_CREATED_AT_1' | translate}} {{entity.createdAt | dateFormatPipe:LL}} {{'ENTITY_CREATED_AT_2' | translate:{guyName:entity.guyName} }} </div>
      
      





優れたインフラストラクチャについて、ngx-translate開発者を称賛してください!


ngx-translateモジュールをアプリケーションにインポートするとき、カスタムTranslateompiler



を提供できます。

ところで、ここに優れた複数の\性別などへのリンクがあります。 ngx-translateで動作するコンパイラ。







私たちのタスクは、ngx-translateローカライズ内でパイプを実行できるTranslateompilerを作成することです。

まず、DIを準備し(Injector`aからパイプを取得するため)、ngx-translateを初期化することから始めましょう。







モジュールのプロバイダーに目的のパイプを追加します。私の場合、アプリケーションには複数のモジュールが含まれているため、これはSharedModule



です。







 @NgModule({ //       imports: [ ...exportedModules ], declarations: [ ...exportedDeclarations ], exports: [ ...exportedModules, ...exportedDeclarations ], providers: [ // declare pipes available with injector [ DateFormatPipe, {provide: 'dateFormat', useExisting: DateFormatPipe} ] ] }) export class SharedModule { }
      
      





ngx-translateを初期化し、 CustomTranslateCompiler



をポイントします。

私の場合、 CoreModule



を使用していCoreModule









 import { CustomTranslateCompiler } from 'app/_core/services/translate/translate.compiler'; @NgModule({ // some needed providers imports: [ CommonModule, TranslateModule.forRoot({ compiler: { provide: TranslateCompiler, useClass: CustomTranslateCompiler, deps: [Injector] }, }) ], exports: [CommonModule, TranslateModule] }) export class CoreModule { }
      
      





モジュールのプロバイダーで宣言されているパイプのみを使用できます(パイプの例-dateFormst)。







 "ENTITY_CREATED_AT": "This entity created at {{createdAtDate | dateFormat:LL}} by {{guyName}}"
      
      





デフォルトでPipeTranslateCompiler



定義しましょう。







 export class PipeTranslateCompiler implements TranslateCompiler { constructor(private injector: Injector, private errorHandler: ErrorHandler) { } public compile(value: string, lang: string): string | Function { return value; } public compileTranslations(translations: any, lang: string): any { return translations; } }
      
      





2つの機能があります。

compileTranslations-入力時にロードされたjsonを取得します(TranslateHttpLoaderからダウンロードできます)。

compile-1つの値のみを取得し、 TranslateService.set('some translate val', 'key', 'en')



介してngx-translateに明示的にフィールドを追加すると呼び出されます。







十分に大きいjsonはcompileTranslationsに入る可能性があるため、オブジェクト全体のフィールドを再帰的に調べて各値を解析する必要があります。どのフィールドを解析し、どのフィールドを解析しないかを事前に知りたいです。 したがって、PipeTranslateCompilerで保存する必要があるすべてのフィールドを@でマークすることにしました。







 "@ENTITY_CREATED_AT": "This entity created at {{createdAtDate | dateFormat:LL}} by {{guyName}}", //     "OTHER_KEY": "This is regular entity" //    
      
      





では、 compile - string | Function



戻り値の型に注意しましょうcompile - string | Function



compile - string | Function



、これは、任意の文字列値を関数に変換できることを意味します.HTMLビューでngx-translateの変換パイプまたはディレクティブを使用すると、関数が何らかのキーに配置されている場合、パラメーターで呼び出します。 translateParamsに渡します。







 <div>{{'ENTITY_CREATED_AT' | translate:paramsObject}}</div> //  ENTITY_CREATED_AT -  transform      paramsObject.
      
      





そのため、 "This entity created at {{createdAtDate | dateFormat:LL}} by {{guyName}}"



という文字列を解析し、関数で置き換える必要があります。







最初に、文字列の解析結果を保存する方法を想像してみましょう。これにより、後でパラメーターに基づいて、既に適用されたパイプを含む文字列をすばやく取得できます。

2+個のパラメーターを持つ2+個のパイプをすぐに解析できるようにしたい







 "SOME_KEY": "value1: {{dateField | dateFormat:LL}} and value2: {{anotherField | customPipe:param1:param2}}
      
      





そのため、括弧{{。*}}内の部分を強調表示して、文字列を正規表現で解析する必要があります。

強調表示されたブラケットごとに、内部の内容、パイプの名前、パイプパラメーターの配列、オブジェクトフィールドの名前に関する情報が必要です。







 {{__ | _:1:2}}
      
      





便宜上、いくつかのインターフェースを定義しましょう。







 interface PipedObject { property: string; // __ pipe: PipeDefinition; //  } interface PipeDefinition { name: string; // _ params: string[]; //   }
      
      





解析関数:







 private parseTranslation(res: string): {pipedObjects: PipedObject[], matches: string[]} { //   "{{dateField | dateFormat:LL | additionalPipe:param1}}", "{{anotherField | customPipe:param1:param2}}" let matches = res.match(/{{.[^{{]*}}/g); let pipedObjects: PipedObject[] = []; (matches || []).forEach((v) => { //     {{dateField | dateFormat:LL}} -> dateField|dateFormat:LL|additionalPipe:param1 v = v.replace(/[{}\s]+/g, ''); //  : dateField|dateFormat:LL|additionalPipe:param1 let pipes = v.split('|'); let objectPropertyName = pipes[0]; // dateField pipes = pipes.slice(1); // [dateFormat:LL, additionalPipe:param1] for(let pipe of pipes) { // customPipe:param1:param2 -> ['customPipe', 'param1', 'param2'] let pipeTokens = pipe.split(':'); pipedObjects.push({ property: objectPropertyName, pipe: { name: pipeTokens[0], // customPipe params: pipeTokens.slice(1) // ['param1', 'param2'] } }); } }); return {pipedObjects, matches}; }
      
      





さて、まずは、parseTranslationと、1つの値のみをコンパイルする関数をバインドします-compile







 public compile(value: string, lang: string): string | Function { return this.compileValue(value); } private compileValue(val): Function { let parsedTranslation = this.parseTranslation(val); // ,    return (argsObj: object) => { //  -     ngx-translate        //      : value1: {{dateField | dateFormat:LL}} and value2: {{anotherField | customPipe:param1:param2}} let res = val; parsedTranslation.pipedObjects.forEach((o, i) => { //   DI   ,        . const pipe = this.injector.get(o.pipe.name); const property = argsObj[o.property]; // argsObj -  -   [translateParams] //           pipe.transform(prop, o.pipe.params); const pipeParams = this.assignPipeParams(argsObj, o.pipe.params || []); if(!property) { return res; } let pipedValue = pipe.transform( property, pipeParams.length === 1 ? pipeParams[0] : pipeParams ); //  {{*.}}    . {{dateField | dateFormat:LL}} -> 15:00 res = res.replace(parsedTranslation.matches[i], pipedValue); }); return res; }; } private assignPipeParams(obj: object, params: string[]) { let assignedParams = []; params.forEach(p => { if(obj.hasOwnProperty(p)) { assignedParams.push(obj[p]); } else { assignedParams.push(p); } }); return assignedParams; }
      
      





明確にするために、assignPipeParamsを使用すると、コンポーネントからcustomPipeにパラメーターを渡すことができます。







 "@SOME": "value1: {{dateField | dateFormat:LL}} and value2: {{anotherField | customPipe:param1}} //      :LL  :param1 -        <div>{{'@SOME' | myTranslate:{dateField: entity.value1, anotherField: entity.value2, param1: 'DYNAMIC_VALUE'} }}</div> //    :param1  
      
      





compileTranslationsからすべてのjsonを解析する方法については説明しません。スタックオーバーフローでは、json構造全体をトラバースするための多くのメソッドを見つけることができます。







 public compileTranslations(translations: any, lang: string): any { this.iterateTranslations(translations); return translations; } private iterateTranslations(obj) { for (let key in obj) { if (obj.hasOwnProperty(key)) { const val = obj[key]; const isString = typeof val === 'string'; if(key[0] === '@' && isString) { obj[key] = this.compileValue(val); //   compileValue } else if(!isString) { this.iterateTranslations(val); } } } }
      
      





できた! ngx-translate json内でパイプを使用できるようになりました!







新しい便利なAngular 6機能

ここでは、Angular Elementsとは何かを簡単に、本質的に見ることができます。

ビデオからのソースがあります







一般に、Angular Elementsはまだ実験的であるため、AngularからWebコンポーネントをコンパイルしてJavaScriptアプリケーションで使用することはできません。







しかし! しかし、その利点はすでにあります。 これで、Angular 5のセルフブートストラップコンポーネントを作成でき、初期化段階でのみアプリケーション全体のブートストラップを行いましたが、アプリケーションをHTML文字列から実行している間はいつでもコンポーネントを「コンパイル」できます。







これは、特定のHTML文字列をAPIからコンポーネントに変換する必要がある場合に役立ちます。







ただし、ComponentTranslateCompilerの作成にも役立ちます。 ngx-translate json`ovからコンポーネントを収集できます。







以上です。 研究と改善!







全ソース








All Articles