我正在尝试在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
消除了该错误。我到底在做什么错了?
答案 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,000多个块。哎呀。
这些块只是以数字作为名称。
再次对救援发表魔术般的评论:
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问题的确切方法。希望如果其他人正在寻找翻译解决方案,它将对他们有所帮助。