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

cyej8jka  于 2022-12-19  发布在  TypeScript
关注(0)|答案(5)|浏览(106)

我尝试在Angular 9(基于monorepo)应用中动态导入locale,我做了如下操作:

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/某存储库/节点模块/@Angular /公共/区域设置/zu. d. ts模块构建失败(来自/home/me/某存储库/节点模块/@ngtools/webpack/src/index. js):错误:TypeScript编译中缺少/home/me/somerepo/node_modules/@angular/common/locales/zu. d. ts。请通过"files"或"include"属性确保它位于您的tsconfig中。
注解掉导入并调用registerLocaleData可以消 debugging 误。我到底做错了什么?

qni6mghb

qni6mghb1#

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

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个块。
1.这些组块只是作为名称给出的数字。
魔术评论再次出手相救:

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]);

这将逻辑从指定忽略哪些文件改为指定加载哪些文件,结果块大小约为100 Kb,而不是6 Mb。

xoefb8l8

xoefb8l82#

我采用了一种不同的方法,决定使用@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/i18 l/目录添加到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/enviornation.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>

设置翻译表单和应用程序初始化。这只是一个简单的例子。然后你可以按照文档如何在你的应用程序中使用翻译服务。
我从来没有使用过@angular/common/locale。我意识到这可能不是一个确切的修复webpack的问题。希望它可以帮助其他人,如果他们正在寻找翻译解决方案。

iih3973s

iih3973s3#

我可以用这个把我的特定语言注册到angular的pipe中

try {
  // register locale for pipes
  const localeModule = await
  import (
    /* webpackInclude: /\b(en|en-GB|fr|de|ru|sv|lt|it|es|ja|sk|pl|ca|nl|tr)\.mjs/ */
    `/node_modules/@angular/common/locales/${locale}`
  );
  registerLocaleData(localeModule.default);
} catch {
  console.warn('Translation file error found for Angular pipes.');
}

我试着用字符串正则表达式符号^开始显式的单词匹配,但是由于某种原因,它没有通过。所以我试着用单词边界正则表达式符号\b,看起来很有效。如果你想消除其他嵌套的文件夹,你可能需要添加一个/* webpackExclude: /folder\/.*\.mjs/ */

mbyulnm0

mbyulnm04#

我有一个以上所有的组合,但不断得到区域设置未加载,所以我现在有一个运行的版本,或多或少像这些变化:

  • 文件名(main.ts除外)主要用于示例 *

tsconfig.json中,我将模块更改为esnext

"module": "esnext",

添加了一个函数,用于在src/app/services/locale.service.ts中动态导入语言环境(Angular 14已验证)

export const localesList = ['en', 'en_US', 'et'];
export const initializeLocales = async (): Promise<Promise<void[]>> => {
    return Promise.all(
        localesList.map(async (locale) => {
            await import(
                /* webpackInclude: /(en|en_US|et)\.mjs$/ */
                `/node_modules/@angular/common/locales/${locale}`
            )
                .then((module) => {
                    registerLocaleData(module.default);
                })
                .catch(() => null);
        })
    );
};

main.ts中(如何避免未加载语言环境)

initializeLocales().then(() => {
    platformBrowserDynamic().bootstrapModule(AppModule);
});

app.module.ts中,我们加载提供程序:

providers: [
    {
        provide: LOCALE_ID,
        deps: [LocaleService],
        useFactory: (localeService: LocaleService): string => localeService.getLocale(),
    },
],

还有一个locale服务,用于存储当前的locale,src/app/services/locale.service.ts

@Injectable({
   providedIn: 'root',
})
export class LocaleService {
    public locale: string | null;

    getLocale(): string {
          //retrieve it from localstorage or somewhere else
        return this.locale as string;
    }

    setLocale(locale: string): void {
        // additionally set it in localStorage or a cookie or something
        this.locale = locale;
    }
}
ff29svar

ff29svar5#

我从几个答案中得出的综合解决方案是
app.module.ts中的提供程序

{
  provide: LOCALE_ID,
  useValue: navigator.language
}

然后在app.component.ts的构造函数中

constructor(@Inject(LOCALE_ID) private locale: string) {
 import(
    /* webpackInclude: /(.*)\.js$/ */
    `@angular/common/locales/${locale.substring(0, 2)}.js`
  ).then(module => registerLocaleData(module.default))
}

然后希望angular为我的每个客户提供一个区域设置,因为此解决方案没有fallbak

相关问题