如何在Angular 9中动态导入语言环境?

时间:2020-04-04 22:28:22

标签: angular typescript

我正在尝试在Angular 9(基于Monorepo)应用程序中动态导入语言环境。我正在执行以下操作:

import { Injectable } from '@angular/core';
import { registerLocaleData } from '@angular/common';

@Injectable()
export class LocaleService {
    ...

    private capitalize(str: string): string {
        return str.charAt[0].toUpperCase() + str.substring(1).toLowerCase();
    }

    registerLocales() {
        for (const lang of ['de', 'fr', 'es']) {
            const basePkg = `locale${this.capitalize(lang)}`;
            const extraPkg = basePkg + 'Extra';

            const base = import(`@angular/common/locales/${lang}`).then(m => m[basePkg]);
            const extra = import(`@angular/common/locales/extra/${lang}`).then(m => m[extraPkg]);

            registerLocaleData(base, extra);
        }
    }
}

代替:

import { Injectable } from '@angular/core';
import { registerLocaleData } from '@angular/common';

import localeDe from '@angular/common/locales/de';
import localeDeExtra from '@angular/common/locales/extra/de';
import localeEs from '@angular/common/locales/es';
import localeEsExtra from '@angular/common/locales/extra/es';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';

@Injectable()
export class LocaleService {
    ...

    registerLocales() {
        registerLocaleData(localeDe, localeDeExtra);
        registerLocaleData(localeEs, localeEsExtra);
        registerLocaleData(localeFr, localeFrExtra);
    }
}

即使在此代码中执行,我也会收到大量由于表单导入而引起的错误:

警告输入 /home/me/somerepo/node_modules/@angular/common/locales/zu.d.ts模块 构建失败(来自 /home/me/somerepo/node_modules/@ngtools/webpack/src/index.js): 错误: /home/me/somerepo/node_modules/@angular/common/locales/zu.d.ts 在TypeScript编译中丢失。请确保它在 您的tsconfig通过“文件”或“包含”属性。

注释导入并调用registerLocaleData消除了该错误。我到底在做什么错了?

2 个答案:

答案 0 :(得分:6)

Eliseo的评论提到的excellent article就是答案。 Typescript的import函数不是普通的函数调用。简而言之,这里发生的事情是import告诉Webpack为与参数中的模式匹配的一切创建块。这是一个问题,因为该模式与locales目录中的所有.d.ts文件匹配,而我们实际上只希望.js文件。解决方案是使用Webpack的“ magic comments”。以下内容足以使所有内容正确加载:

const base = import(
  /* webpackExclude: /\.d\.ts$/ */
  `@angular/common/locales/${key}`).then(m => m[basePkg]);

const extra = import(
  /* webpackExclude: /\.d\.ts$/ */
  `@angular/common/locales/extra/${key}`).then(m => m[extraPkg]);

但是...有两个问题。

  1. 每个语言环境都变成了一块。这将创建1,000多个块。哎呀。

  2. 这些块只是以数字作为名称。

再次对救援发表魔术般的评论:

const base = import(
  /* webpackExclude: /\.d\.ts$/ */
  /* webpackMode: "lazy-once" */
  /* webpackChunkName: "i18n-base" */
  `@angular/common/locales/${key}`).then(m => m[basePkg]);

const extra = import(
  /* webpackExclude: /\.d\.ts$/ */
  /* webpackMode: "lazy-once" */
  /* webpackChunkName: "i18n-extra" */
  `@angular/common/locales/extra/${key}`).then(m => m[extraPkg]);

这变得更近了,创建了两个块而不是数千个,但是它们很大。如果我们知道我们感兴趣的语言环境,我们可以做得更好。这是最终版本:

const base = import(
  /* webpackInclude: /(de|en|es|fr|it|nl|no|pl|pt-BR|pt|fi|sv|ko|ru|zh|zh-Hans|zh-Hant|ja)\.js/ */
  /* webpackMode: "lazy-once" */
  /* webpackChunkName: "i18n-base" */
  `@angular/common/locales/${key}`).then(m => m[basePkg]);

const extra = import(
  /* webpackInclude: /(de|en|es|fr|it|nl|no|pl|pt-BR|pt|fi|sv|ko|ru|zh|zh-Hans|zh-Hant|ja)\.js/ */
  /* webpackMode: "lazy-once" */
  /* webpackChunkName: "i18n-extra" */
  `@angular/common/locales/extra/${key}`).then(m => m[extraPkg]);

这会将逻辑从指定要忽略的文件更改为指定要加载的文件。这样会产生大约100Kb的块,而不是6Mb。

答案 1 :(得分:4)

我采用了另一种方法,并决定使用@ngx-translate来管理翻译和 @ngx-translate/http-loader,可在应用加载时动态加载JSON文件中的翻译。 这将使构建尺寸更小,因为它不会构建/捆绑翻译。它只是将翻译后的json文件复制为资产

文件夹结构如下:

src
├── app
│   ├── ...
│   ├── app.component.html
│   ├── app.component.ts
│   └── app.modules.ts
├── environments
│   └── environment.ts
└── i18l
    ├── en-us.json
    ├── es.json
    └── ...

src / i18l / 目录添加到 angular.json 中的assets

{
  "projects": {
    "your-app": {
      ...
      "architect": {
        ...
        "build": {
          ...
          "options": {
            ...
            "assets": [
              "src/favicon.ico",
              "src/assets",
              "src/i18l" // <-- add this
            ]
          },
        }
      }
    }
  }
}

app.module.ts

中设置翻译模块
// other imports 
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

// AoT requires an exported function for factories
export function HttpLoaderFactory (http: HttpClient) {
  // this tells the translation service what path to fetch the translation from
  return new TranslateHttpLoader(http, 'i18l/', '.json');
}

@NgModule({
  declarations: [...],
  imports: [
    // other imports
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    }),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

我想在我的 environments / enviornment.ts 文件中保留可用翻译的列表:

export const environment = {
  production: false,
  availableTranslations: [
    'en-us',
    'es'
  ]
};

然后,您需要在应用程序加载时选择并加载翻译。为简单起见,这是 app.component.ts

// other imports
import { TranslateService } from '@ngx-translate/core';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  localeForm: FormGroup;
  locales: string[];
  translationLoaded: boolean = false;

  constructor (
    private translateService: TranslateService
  ) { }

  ngOnInit () {
    this.locales = environment.availableTranslations.slice(); //slice to create a copy
    // just a basic form for the user to select a translation
    this.localeForm = new FormGroup({
      locale: new FormControl('en-us', {
        updateOn: 'blur',
        validators: [Validators.required]
      })
    });
  }

  async submitForm (): void {
    // await the http request for the translation file
    await this.translateService.use(this.localeForm.value.locale).toPromise();
    this.translationLoaded = true;
  }
}

为用户创建一个基本表单,以便在 app.component.html

中选择翻译
<!-- if we have loaded a translation, display the app -->
<ng-container *ngIf="translationLoaded">
  <router-outlet></router-outlet>
</ng-container>

<!-- if we haven't loaded the translation, show the translation picker -->
<ng-container *ngIf="!translationLoaded">
  You need to select a language

  <form [formGroup]="localeForm" (ngSubmit)="submitForm()">
    <label for="locale">Select a Language</label>
    <select name="locale" id="locale" formControlName="locale">
      <option *ngFor="let loc of locales" id="{{loc}}" value="{{loc}}">
        {{loc}}
      </option>
    </select>

    <label for="useLocale">Use Language</label>
    <button name="useLocale" type="submit" [disabled]="!localeForm.valid">Select</button>
  </form>

</ng-container>

根据需要设置翻译表单和应用初始化。那只是一个简单的例子。然后,您可以按照documentation了解如何在整个应用中使用翻译服务。

我从未与@angular/common/locale合作。我意识到这可能不是解决webpack问题的确切方法。希望如果其他人正在寻找翻译解决方案,它将对他们有所帮助。