5 votes

Comment importer dynamiquement des locales dans Angular 9 ?

J'essaie d'importer dynamiquement des locales dans une application Angular 9 (basée sur monorepo). Je fais quelque chose comme ce qui suit :

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

au lieu de :

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

Avant même que ce code ne s'exécute, j'obtiens une multitude d'erreurs causées par les importations du formulaire :

AVERTISSEMENT dans /home/me/somerepo/node_modules/@angular/common/locales/zu.d.ts Module a échoué (de /home/me/somerepo/node_modules/@ngtools/webpack/src/index.js) : Erreur : /home/me/somerepo/node_modules/@angular/common/locales/zu.d.ts est absent de la compilation TypeScript. Veuillez vous assurer qu'elle se trouve dans dans votre tsconfig via la propriété 'files' ou 'include'.

En commentant les importations et l'appel à registerLocaleData élimine l'erreur. Mais qu'est-ce que je fais de mal ici ?

9voto

Scott Deerwester Points 86

El excellent article mentionné par le commentaire d'Eliseo a la réponse. Le site de Typescript import n'est pas un appel de fonction ordinaire. En bref, ce qui se passe ici, c'est que import indique à Webpack de créer des morceaux pour tout qui correspond au modèle dans l'argument. C'est un problème, car le motif correspond à tous les éléments de la base de données .d.ts dans le répertoire locales, alors que nous ne voulons en fait que le fichier .js des fichiers. La solution consiste à utiliser la fonction " commentaires magiques ". Ce qui suit est suffisant pour que tout se charge correctement :

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

Mais... il y a quelques problèmes.

  1. Chaque locale est transformée en un chunk. Cela crée plus de 1 000 chunks. Ouch.

  2. Les morceaux reçoivent simplement des numéros comme noms.

Les commentaires magiques à la rescousse, encore une fois :

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

C'est plus proche, créant deux morceaux au lieu de milliers, mais ils sont grand . Si nous savons quels sont les lieux qui nous intéressent, nous pouvons faire beaucoup mieux. Voici la version finale :

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

Cela change la logique de spécifier les fichiers à ignorer à spécifier les fichiers à charger. Il en résulte des morceaux d'environ 100Kb au lieu de 6Mb.

5voto

DJ House Points 849

J'ai adopté une approche différente et décidé d'utiliser @ngx-translate pour gérer les traductions et le site @ngx-translate/http-loader pour charger dynamiquement les traductions à partir d'un fichier JSON au chargement de l'application. Cela permet de réduire la taille de l'application parce que les traductions ne sont pas construites ou regroupées. Elle copie simplement les fichiers JSON traduits en tant que ressources.

La structure du dossier ressemble à ceci :

src
 app
    ...
    app.component.html
    app.component.ts
    app.modules.ts
 environments
    environment.ts
 i18l
     en-us.json
     es.json
     ...

Ajouter le src/i18l/ pour assets en angular.json :

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

Configurer le module de traduction dans 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 { }

J'aime garder une liste des traductions disponibles dans ma page d'accueil. environnements/enviornment.ts fichier :

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

Vous devez ensuite sélectionner et charger la traduction à un moment donné lors du chargement de l'application. Pour simplifier, voici un exemple dans 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;
  }
}

Créez un formulaire de base pour que l'utilisateur puisse sélectionner la traduction en 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>

Configurez le formulaire de traduction et l'initialisation de l'application comme vous le souhaitez. Il s'agit d'un exemple simple. Vous pouvez ensuite suivre le documentation sur la façon d'utiliser le service de traduction dans votre application.

Je n'ai jamais travaillé avec @angular/common/locale . Je réalise que ce n'est peut-être pas une solution exacte pour le problème de webpack. J'espère que cela pourra aider d'autres personnes si elles recherchent des solutions de traduction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X