الزاوي: ترجمة ngx. تحسين البنية التحتية باستخدام Webpack

يوم جيد.


حان الوقت لاختراق الحياة ngx-translate. في البداية ، خططت لثلاثة أجزاء ، ولكن لأن الجزء الثاني ليس في الواقع غني بالمعلومات - في هذا سأحاول تلخيص الجزء الثاني بإيجاز قدر الإمكان.


الجزء الأول


خذ بعين الاعتبار AppTranslateLoader لاستبدال TranslateHttpLoader . AppTranslateLoader أولاً اهتمامًا للغة المتصفح وسيحتوي على منطق احتياطي ، واستيراد توطين MomentJs ، وتحميله عبر APP_INITIALIZER. أيضًا ، نتيجة للجمع بين جزأين من عمليات اختراق الحياة ، على طول الطريق سوف نتعمق في إنشاء بنية تحتية مريحة ومرنة في المشروع.


الهدف الرئيسي ليس AppTranslateLoader (لأنه بسيط للغاية وليس من الصعب صنعه) ، ولكن إنشاء البنية التحتية.


حاولت الكتابة قدر الإمكان ، ولكن لأن المقالة تحتوي على الكثير من الأشياء التي يمكن وصفها بمزيد من التفصيل - سيستغرق الأمر الكثير من الوقت ولن يكون مثيرًا للاهتمام لأولئك الذين يعرفون بالفعل كيف). لذلك ، لم تكن المقالة ودية للغاية للمبتدئين. من ناحية أخرى ، في النهاية هناك رابط لمثال prodg.


قبل البدء ، أود أن أشير إلى أنه بالإضافة إلى تنزيل اللغات عبر http ، من الممكن كتابة محمل بطريقة تقوم بتحميل اللغات الضرورية في مجموعتنا في مرحلة التجميع. وبالتالي ، لا تحتاج إلى إضافة أي لوادر عبر http ، ولكن من ناحية أخرى ، مع هذا النهج ، ستحتاج إلى إعادة إنشاء التطبيق في كل مرة نقوم فيها بتغيير ملفاتنا مع التعريب ، وأيضًا ، يمكن أن يؤدي ذلك إلى زيادة كبيرة في حجم حزمة .js.


 // webpack-translate-loader.ts import { TranslateLoader } from '@ngx-translate/core'; import { Observable } from 'rxjs/Observable'; export class WebpackTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable<any> { return Observable.fromPromise(System.import(`../assets/i18n/${lang}.json`)); } } 

إذا أقسم IDE على System فأنت بحاجة إلى إضافته إلى typings.d.ts:


 declare var System: System; interface System { import(request: string): Promise<any>; } 

الآن يمكننا استخدام WebpackTranslateLoader في app.module:


 @NgModule({ bootstrap: [AppComponent], imports: [ TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: WebpackTranslateLoader } }) ] }) export class AppModule { } 

AppTranslateLoader


لذا ، فلنبدأ في كتابة AppTranslateLoader . بادئ ذي بدء ، أريد تحديد العديد من المشاكل التي يجب مواجهتها باستخدام TranslateHttpLoader القياسي:


  • ترجمة الخفقان. لا يعرف TranslateHttpLoader كيفية التنفيذ كجزء من عملية تهيئة التطبيق ويمكننا الدخول في موقف عندما نرى بعد التهيئة أن لدينا مكان للتسميات الصحيحة في التطبيق - المفاتيح (MY_BUTTON_KEY هو مكان الزر الخاص بي) ، والتي تتغير بعد لحظة إلى النص الصحيح.


  • التواريخ سيكون من الجميل أن يكون لديك خدمة تحول توطين التواريخ. عندما يتعلق الأمر بتعريب النص ، فعلى الأرجح سيتعين عليك الاهتمام بتوطين التواريخ والأوقات وما إلى ذلك. يمكنك استخدام momentJs أو حل i18n المدمج في Angular. كلا الحلين جيدان ، ولديهما أنابيب Angular 2+ للتنسيق في طرق العرض.


  • التخزين المؤقت. باستخدام TranslateHttpLoader ، يجب عليك تكوين خادم FE لتخزين حزم json الخاصة بك بشكل صحيح. خلاف ذلك ، سوف يرى المستخدمون الإصدارات القديمة من الأقلمة ، والأسوأ أنهم سيرون مفاتيح الأقلمة (إذا تمت إضافة مفاتيح جديدة بعد التخزين المؤقت من قبل المستخدم). لا أريد أن أزعج في كل مرة أقوم فيها بالنشر على خادم جديد لحظة إعداد التخزين المؤقت. لذلك سنجعل Webpack يفعل كل شيء بالنسبة لنا بالطريقة التي يفعلها لحزم .js.

مسودة AppTranslateLoader


حلول للمشاكل:

1. ترجمة مشكلة الخفقان - استخدم AppTranslateLoader كجزء من APP_INITIALIZER

شارك APP_INITIALIZER أيضًا بنشاط في مقالة حول الرمز المميز للتحديث ، إن لم يكن لذيذًا بشأن المُهيئ - أنصحك بقراءة المقالة على الرغم من حقيقة أنها تتعلق بالرمز المميز للتحديث. في الواقع ، قرار استخدام المُهيئ واضح جدًا (لأولئك الذين لديهم مُهيئ) ، ولكن ما زلت آمل أن يكون هناك أشخاص في متناول اليد:


 //app.module.ts export function translationLoader(loader: AppTranslateLoader) { return () => loader.loadTranslation(); } @NgModule({ bootstrap: [AppComponent], providers: [ { provide: APP_INITIALIZER, useFactory: translationLoader, deps: [AppTranslateLoader], multi: true } ] }) export class AppModule { } 

2. مشكلة التمر. سنقوم فقط بتغيير اللغة في لحظات Js مع ngx-tranlate.

كل شيء بسيط هنا - بعد تحميل json مع الأقلمة ، نقوم فقط بتحويل الأقلمة إلى momentJs (أو i18n).


من الجدير بالذكر أيضًا أن MomenceJs ، مثل i18n ، يمكنها استيراد التوطين بشكل منفصل ، ويمكن أيضًا لحزمة MomJs استيراد حزمة ، ولكن حزمة الترجمات بأكملها تستغرق ~ 260 كيلوبايت ، ولا تحتاج إلا إلى اثنتين منها.


في هذه الحالة ، يمكنك استيراد اثنين منهم فقط مباشرة في الملف حيث AppTranslateLoader التصريح عن AppTranslateLoader .


 import 'moment/locale/en-gb'; import 'moment/locale/ru'; 

الآن ستكون en-gb و ru locations في حزمة تطبيقات js. في AppTranslateLoader يمكنك إضافة معالج لغة AppTranslateLoader التحميل:


 export Class AppTranslateLoader { // .... private onLangLoaded(newLang: string) { //     if (this.loadedLang && this.loadedLang !== newLang) { this.translate.resetLang(this.loadedLang); } this.loadedLang = newLang; this.selectedLang = newLang; // TODO:       //     ,       //  en  ru,  momentJs   en. moment().locale(newLang); //  .  momentJs localStorage.setItem(this.storageKey, newLang); //   ls this.loadSubj.complete(); //   -      . } 

!!! هذا المعالج لديه عيب: إذا تم توفير الترجمة باللغة المحلية فقط في مشروعنا لترجمة ngx ، ولكن على سبيل المثال لحظة أو تحتاج إلى استخدام إما en أو en-gb ، يجب توسيع منطق المعالج ، أو يجب توفير en-localization لـ en-gb في غضون ترجمة ngx.


!!! في الوقت الحالي باستخدام // TODO: يمكننا كتابة مكون إضافي لحزمة الويب ، سننظر في بعض المكونات الإضافية لاحقًا ، ولكن ليس لدي هذا حتى الآن.


تسأل ، لماذا من المستحيل تحميل توطين التواريخ والوقت وكذلك توطين النص في الواجهة (ديناميكيًا ، عبر HTTP)؟ وذلك لأن تعريب التاريخ يحتوي على منطقه الخاص ، وبالتالي يتم تقديمه ككود جافا سكريبت .


ولكن على الرغم من ذلك ، هناك طريقة لتحميل مثل هذه التوطين عن طريق كتابة كود صغير "قذر". لا أستخدم هذا الرمز في الإنتاج ، لكن الترجمة الثانية داخل حزمة بلدي لا تزعجني. ولكن إذا كان لديك العديد من الترجمات ، فأنت تريد تحميلها ديناميكيًا وليس بشكل آمن للغاية ، ضع في اعتبارك:


 private async loadAngularCulture(locale) { let angularLocaleText = await this.httpClient.get(`assets/angular-locales/${locale}.js`).toPromise(); // extracting the part of the js code before the data, // and i didn't need the plural so i just replace plural by null. const startPos = angularLocaleText.indexOf('export default '); angularLocaleText = 'return ' + angularLocaleText.substring(startPos + 15).replace('plural', null); // The trick is here : to read cldr data, i use a function const f = new Function(angularLocaleText); const angularLocale = f(); // console.log(angularLocale); // And now, just registrer the object you just created with the function registerLocaleData(angularLocale); } 

آخر مرة قمت باختبار هذه الطريقة في Angular 4. على الأرجح أنها تعمل الآن.


لسوء الحظ ، فإن هذا الاختراق القذر لن يعمل في حالة c momentJs (التوطين الزاوي فقط). على الأقل لم أتمكن من العثور على طريقة للقيام بذلك ، ولكن إذا كنت مبرمجًا للقراصنة ملتحين للغاية ، فسأكون سعيدًا لرؤية الحل في التعليقات.


3. التخزين المؤقت. على غرار إنشاء حزمة .js ، يمكنك إضافة تجزئة لاسم حزمة json.

كل هذا يتوقف على كيفية جمع كل json'es في ملف واحد ، ربما لديك كل شيء في ملف واحد. في الإنترنت ، يمكنك العثور على عدد من وحدات npm التي يمكنها جمع json صغير في ملف واحد. لم أجد من يستطيع إرفاق التجزئة وجمع كل شيء في ملف واحد. لا يمكن لـ Webpack نفسها أيضًا التعامل مع json كما هو مطلوب في تفاصيل ترجمة ngx. لذلك ، سنكتب البرنامج المساعد لدينا webpack.


باختصار: نحتاج إلى جمع كل json في المشروع وفقًا لنمط معين ، بينما نحتاج إلى تجميعها بالاسم (en ، ru ، de ، إلخ) لأنه ، على سبيل المثال ، يمكن أن يكون en.json في مجلدات مختلفة. ثم ، لكل ملف تم جمعه ، تحتاج إلى إرفاق تجزئة.


هناك مشكلة هنا. كيف يتعرف AppTranslateLoader أسماء الملفات إذا كان لكل AppTranslateLoader اسمها الخاص؟ على سبيل المثال ، بما في ذلك الحزمة في index.html ، يمكننا تضمين HtmlWebpackPlugin ونطلب منه إضافة علامة نصية باسم الحزمة من تلقاء نفسها.


لحل هذه المشكلة لترجمات .json ، سيقوم المكون الإضافي webpack بإنشاء config.json ، والذي سيحتوي على اقتران رمز اللغة باسم ملف التجزئة:


 { "en": "en.some_hash.json", "ru": "ru.some_hash.json" } 

سيتم تخزين config.json أيضًا مؤقتًا بواسطة المتصفح ، ولكنه سيستغرق القليل ويمكننا ببساطة تحديد معلمة queryString عشوائية عند زيادة حجم هذا الملف (وبالتالي تحميله باستمرار مرة أخرى). أو قم بتعيين معرف عشوائي لـ config.json (سأصف هذه الطريقة ، يمكن العثور على الأول في Google).


أريد أيضًا تبسيط البنية التحتية وذرية التوطين قليلاً. سوف json مع الترجمة في المجلد مع مكونه. ولتجنب المفاتيح المكررة ، سيتم بناء بنية حزمة json بناءً على المسار إلى ملف json محدد. على سبيل المثال ، لدينا اثنان en.json ، أحدهما يكمن في المسار src/app/article-component element ، والآخر src/app/comment-component . أريد الحصول على json التالي في الإخراج:


 { "article-component": { "TITLE": "Article title" }, "comment-component": { "TITLE": "Comment title" } } 

يمكننا تجاهل الجزء من المسار الذي لا نحتاجه حتى تكون المفاتيح قصيرة قدر الإمكان في طرق العرض.


!!! هناك عيب: عند وضع المكون في مجلد آخر ، سيتغير مفتاح الترجمة.


في وقت لاحق ، سننظر في اختراق آخر للحياة يسمح لنا بالإشارة في المكون إلى الحقل الرئيسي الأخير فقط ، بغض النظر عن مكان ومدى عمق مشروعنا في المشروع ، وبالتالي يمكننا نقله كما نحب وإعادة تسميته كما نرغب.


في الأساس ، أريد تحقيق التغليف وحتى تلميحًا لتعدد الأشكال الخاص بترجمة ngx-translations. يعجبني مفهوم تغليف العروض في Angular - Angular View Encapsulation ، أو بالأحرى DOM الظل . نعم ، هذا يزيد من حجم التطبيق ككل ، ولكن سأقول مسبقًا أنه بعد أن أصبح ngx-translate أكثر تغليفًا ، أصبح العمل مع ملفات التوطين أكثر متعة. بدأت المكونات في الاهتمام فقط بالتوطين الخاص بها ، بالإضافة إلى أنه سيكون من الممكن إعادة تعريف التوطين في المكون الفرعي اعتمادًا على التوطين في المكون الرئيسي. أيضًا ، يمكنك الآن نقل المكونات من مشروع إلى مشروع ، وستتم ترجمتها بالفعل. ولكن كما هو الحال في أي مكان آخر ، هناك فروق دقيقة في ذلك لاحقًا.


لذا دعنا ننتقل إلى المكون الإضافي. ما هو وكيف . دمج المساعد التعريب .
يمكن العثور على رمز المصدر الخاص بالمحمل والمكوِّن الإضافي في رابط المثال الموجود أسفل المقالة (المجلد ./build-utils).


يعمل المكون الإضافي كل ما يتعلق بما هو مكتوب أعلاه ، ويقبل الخيارات التالية:


  • حذف. الأسماء في مسار التوطين التي يجب تجاهلها (هذه هي اللحظة التي أريد فيها إزالة الأجزاء الإضافية من المسار إلى الملف)
  • إدخال الملف. عادي لجلب ملفات التوطين في prodge (مثل الاختبار في حزمة الويب)
  • rootDir. من أين تبدأ البحث عن الملفات بنمط fileInput
  • الإخراج حيث سيتم إنشاء ملف التكوين والتعريب في مجلد dist
  • configName. تحت أي اسم سيتم إنشاء ملف التكوين.

في مشروعي ، يتم توصيل المكون الإضافي بهذه الطريقة:


 // build-utils.js // part of METADATA { // ... translationsOutputDir: 'langs/', translationsFolder: '@translations', translationsConfig: `config.${Math.random().toString(36).substr(2, 9)}.json`, } //webpack.common.js new MergeLocalizationPlugin({ fileInput: [`**/${METADATA.translationsFolder}/*.json`, 'app-translations/**/*.json'], rootDir: 'src', omit: new RegExp(`app-translations|${METADATA.translationsFolder}|^app`, 'g'), outputDir: METADATA.translationsOutputDir, configName: METADATA.translationsConfig }), 

داخل المكونات التي تحتاج إلى الترجمة يوجد مجلد @translations ، ويحتوي على en.json ، ru ، إلخ.


ونتيجة لذلك ، عند التقليب ، سيتم جمع كل شيء في ملف واحد ، مع مراعاة المسار إلى المجلد @translations . ستكون حزمة الترجمة باللغة dist / langs / ، وستتم تسمية التهيئة باسم config. $ {Some-random} .json.


بعد ذلك ، سنتأكد من تحميل حزمة الأقلمة المطلوبة في التطبيق. هناك نقطة هشة - فقط حزمة الويب تعرف عن مسار التوطين واسم ملف التكوين ، دعنا نأخذ هذا في الاعتبار حتى تدخل أحدث البيانات إلى AppTranslateLoader وليس هناك حاجة لتغيير الأسماء في مكانين.


 // some inmports // ... // momentJs import * as moment from 'moment'; import 'moment/locale/en-gb'; import 'moment/locale/ru'; @Injectable() export class AppTranslateLoader { //            public additionalStorageKey: string = ''; private translationsDir: string; private translationsConfig: string; private selectedLang: string; private fallbackLang: string; private loadedLang: string; private config: { [key: string]: string; } = null; private loadSubs = new Subscription(); private configSubs = new Subscription(); private loadSubj = new Subject(); private get storageKey(): string { return this.additionalStorageKey ? `APP_LANG_${this.additionalStorageKey}` : 'APP_LANG'; } constructor(private http: HttpClient, private translate: TranslateService) { //   webpack       //     . this.translationsDir = `${process.env.TRANSLATE_OUTPUT}`; this.translationsConfig = `${process.env.TRANSLATE_CONFIG}`; this.fallbackLang = 'en'; const storedLang = this.getUsedLanguage(); if (storedLang) { this.selectedLang = storedLang; } else { this.selectedLang = translate.getBrowserLang() || this.fallbackLang; } } } 

process.env.TRANSLATE_OUTPUT لن يعمل ، نحتاج إلى الإعلان عن مكون إضافي آخر في حزمة الويب (DefinePlugin أو EnvironmentPlugin):


 // METADATA declaration const METADATA = { translationsOutputDir: 'langs/', translationsFolder: '@translations', translationsConfig: `config. ${Math.random().toString(36).substr(2, 9)}.json`, }; // complex webpack config... // webpack plugins... new DefinePlugin({ 'process.env.TRANSLATE_OUTPUT': JSON.stringify(METADATA.translationsOutputDir), 'process.env.TRANSLATE_CONFIG': JSON.stringify(METADATA.translationsConfig), }), 

الآن يمكننا تغيير مسار التوطين واسم التكوين في مكان واحد فقط.
بشكل افتراضي ، من البيع الزاوي الافتراضي الذي تم إنشاؤه في تجميع حزمة الويب ( ng eject ) ، لا يمكنك تحديد process.env.someValue من الكود (حتى إذا كنت تستخدم DefinePlugin) ، يمكن للمترجم أن يقسم. لكي يعمل هذا ، تحتاج إلى استيفاء شروط 2 أ:


  • في main.ts ، أضف السطر الأول /// <reference types="node"/>
  • يجب أن يكون لدى package.json @types/node - npm install --save-dev @types/node .

ننتقل مباشرة إلى عملية التمهيد.
إذا كنت تنوي استخدام APP_INITIALIZER ، فتأكد من إرجاع Promise ، وليس ملحوظًا. مهمتنا هي كتابة سلسلة استعلام:


  • تحتاج أولاً إلى تنزيل config.json (فقط إذا لم يتم تحميله).
  • حاول تحميل اللغة ، وهي لغة متصفح المستخدم
  • توفير منطق احتياطي بلغة التنزيل الافتراضية.

 // imports @Injectable() AppTranslateLoader { // fields ... //    ,         //      ,   // Subscription    unsubscribe    //   private loadSubs = new Subscription(); private configSubs = new Subscription(); //       -   // Subject       private loadSubj = new Subject(); // constructor ... //  Promise! public loadTranslation(lang: string = ''): Promise<any> { if (!lang) { lang = this.selectedLang; } //       if (lang === this.loadedLang) { return; } if (!this.config) { this.configSubs.unsubscribe(); this.configSubs = this.http.get<Response>(`${this.translationsDir}${this.translationsConfig}`) .subscribe((config: any) => { this.config = config; this.loadAndUseLang(lang); }); } else { this.loadAndUseLang(lang); } return this.loadSubj.asObservable().toPromise(); } private loadAndUseLang(lang: string) { this.loadSubs.unsubscribe(); this.loadSubs = this.http.get<Response>(`${this.translationsDir}${this.config[lang] || this.config[this.fallbackLang]}`) .subscribe(res => { this.translate.setTranslation(lang, res); this.translate.use(lang).subscribe(() => { this.onLangLoaded(lang); }, // fallback  ngx-translate   (err) => this.onLoadLangError(lang, err)); }, // fallback  http   (err) => this.onLoadLangError(lang, err)); } private onLangLoaded(newLang: string) { //     if (this.loadedLang && this.loadedLang !== newLang) { this.translate.resetLang(this.loadedLang); } this.loadedLang = newLang; this.selectedLang = newLang; // TODO:       //     ,       //  en  ru,  momentJs   en. moment().locale(newLang); //  .  momentJs localStorage.setItem(this.storageKey, newLang); //   ls this.loadSubj.complete(); //   -      . } private onLoadLangError(langKey: string, error: any) { //   ,      if (this.loadedLang) { this.translate.use(this.loadedLang) .subscribe( () => this.onLangLoaded(this.loadedLang), (err) => this.loadSubj.error(err)); //    } else if (langKey !== this.fallbackLang) { //      fallback  this.loadAndUseLang(this.fallbackLang); } else { //    this.loadSubj.error(error); } } 

تم.


الآن نعود إلى مشكلة نقل المكونات إلى مجلدات أخرى ، والتغليف وشبه تعدد الأشكال.


في الواقع ، لدينا بالفعل نوع من التغليف. يتم دفع التعريب إلى مجلدات بجوار المكونات ، وجميع المسارات الرئيسية فريدة من نوعها ، ولكن لا يزال بإمكاننا توطين مفاتيح بعض المكون 1 داخل بعض المكون 2 وسيكون من الصعب تتبع كل شيء ، وسوف نكتشف ذلك لاحقًا.


 <some-component1 [someLabel]="'components.some-component2.some_key' | tanslate"></some-component1> // components.some-component2 -     

فيما يتعلق بحركة المكونات:
الآن المفتاح الذي سنستخدمه في العرض مرتبط بشكل صارم بالمسار النسبي لملف التوطين ويعتمد على البنية التحتية المحددة للمشروع.


سأعطي حالة حزينة إلى حد ما من هذا الموقف:


 <div translate="+lazy-module.components.article-component.article_title"></div> 

ولكن ماذا لو قمت بتغيير اسم المجلد المكون إلى ما بعد المكون؟
سيكون من الصعب جدًا إدخال هذا المفتاح في جميع الأماكن الضرورية. بالطبع ، لم يقم أحد بإلغاء لصق النسخ والبحث والاستبدال ، ولكن كتابة هذا دون مطالبات IDE أمر مرهق أيضًا.


لحل هذه المشكلات ، دعنا ننتبه إلى ما تقوم به حزمة الويب حيال ذلك؟ يحتوي Webpack على شيء مثل المُحمل ، وهناك العديد من اللوادر المتاحة التي تعمل على مسارات الملفات: على سبيل المثال ، مسارات الموارد في css - بفضل webpack يمكننا تحديد المسارات النسبية لصورة الخلفية: url (../ نسبيا. png) ، وهكذا بقية مسارات الملف في المشروع في كل مكان!


يعرف من قام ببناء حزم الويب الخاصة به أن المحمل يتلقى ملفًا عند الإدخال يطابق نمطًا معينًا. تتمثل مهمة اللودر نفسه في تحويل ملف الإدخال هذا بطريقة ما وإعادته ، لمزيد من التغييرات بواسطة اللوادر الأخرى.


لذلك ، نحن بحاجة إلى كتابة محمل لدينا. السؤال هو ما نوع الملفات التي سنقوم بتغييرها: طرق العرض أو المكونات؟ من جهة ، يمكن أن تكون المشاهدات مباشرة في المكون وبشكل منفصل. يمكن أن تكون المشاهدات كبيرة بما فيه الكفاية ويصعب تحليلها ، تخيل ما إذا كان لدينا وجهة نظر حيث تترجم 100 توجيه (ليس في حلقة):


 <div id="1">{{'./some_key_1' | translate}}</div> ... <div id="100">{{'../another_key_!' | translate}}</div> 

من خلال اللودر ، يمكننا استبدال المسار الرئيسي لتعريب المكونات بالقرب من كل أنبوب أو توجيه.


 <div id="1">{{'app.some-component.some_key_1' | translate}}</div> // app.some-component. -   loader' 

يمكننا إضافة حقل إلى مكون يوفر الترجمة:


 @Component({ selector: 'app-some', template: '<div>{{(localization + 'key') | tanslate}}</div>' }) export class SomeComponent { localization = './' } 

إنه أمر سيئ أيضًا - عليك إنشاء مفتاح تعريب في كل مكان.


نظرًا لأن أكثر الخيارات وضوحًا تبدو سيئة ، فحاول استخدام مصمم وحفظ بعض البيانات الوصفية في النموذج الأولي للمكون (كما تفعل Angular).


الصورة


التعليقات التوضيحية - البيانات الوصفية للديكور الزاوي
__app_annotations__ - البيانات الوصفية التي سنخزنها لأنفسنا


يمكن كتابة المسار إلى مجلد الترجمة بالنسبة للمكون إلى الديكور ، ويمكن توسيع نفس الديكور بخيارات أخرى باستثناء المسار.


 //translate.service.ts const app_annotations_key = '__app_annotations__'; export function Localization(path: string) { // tslint:disable-next-line:only-arrow-functions return function (target: Function) { const metaKey = app_annotations_key; Object.defineProperty(target, metaKey, { value: { //         path. path, name: 'Translate' } } as PropertyDescriptor); }; } //some.component.ts @Component({...}) @Localization({ path: './', otherOptions: {...} }); export class SomeComponent { } 

webpack, loader , - . , ( styleUrls) . loader, npm . .


, -. , -.


 <div>{{'just_key' | translate}}</div> 

. , , , . — Injector, . , Injector, '' , translate . Injector, ( ), 'get'.


الصورة


, parent , , Injector'a , , , , , .


, API, forwarRef() ( Angular reactive forms, control ). , . .


 // translate.service.ts export const TRANSLATE_TOKEN = new InjectionToken('MyTranslateToken'); // app.component.ts @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [{provide: TRANSLATE_TOKEN, useExisting: forwardRef(() => AppComponent)}] }) @Localization('./') export class AppComponent { title = 'app'; } 

, , , forwardRef().


, Injector forwardRef() , . , '' . , , .


 // my-translate.directive.ts @Directive({ // tslint:disable-next-line:directive-selector selector: '[myTranslate]' }) export class MyTranslateDirective extends TranslateDirective { @Input() public set myTranslate(e: string) { this.translate = e; } private keyPath: string; constructor(private _translateService: TranslateService, private _element: ElementRef, _chRef: ChangeDetectorRef, //    forwardRef() @Inject(TRANSLATE_TOKEN) @Optional() protected cmp: Object) { super(_translateService, _element, _chRef); //    const prototype = Object.getPrototypeOf(cmp || {}).constructor; if (prototype[app_annotations_key]) { //      this.keyPath = prototype[app_annotations_key].path; } } public updateValue(key: string, node: any, translations: any) { if (this.keyPath) { //     ,   //   key = `${this.keyPath.replace(/\//, '.')}.${key}`; } super.updateValue(key, node, translations); } } 

.


- :


 <div>{{'just_this_component_key' | myTranslate}}</div> //  <div myTranslate="just_this_component_key"></div> 

translate , . , , - :


 //en.bundle.json { "global_key": "Global key" "app-component": { "just_key": "Just key" } } //some-view.html <div translate="global_key"></div> 

Research and improve!


full example


:


  1. FE node.js stacktrace.js.
  2. Jest Angular .
  3. Web worker ) , , Angular .

Source: https://habr.com/ru/post/ar413787/


All Articles