Core: How can I lazy load the json file with lazyload module?

Created on 30 Mar 2017  路  8Comments  路  Source: ngx-translate/core

In each lazyload module, have their own json file. I want to load the json file when the lazy module loaded . How could I do?

Most helpful comment

Hi

I have the same issue in lazy load architecture. Problem that current release don't support partial loading from the box. My solution to handle this problem and to keep standard way of using angular + ngx-translate is next:

Structure

/src
- - /app
- - /i18n
- - - - /account
- - - - - - en.json

account/en.json

{
  "hello": "world"
}

SharedModule

imports/exports: TranslateModule

Routes

const routes: Routes = [
{
    path: 'account',
    loadChildren: './account/account.module#AccountModule',
    data: {
      i18n: 'account'
    },
    canActivate: [TranslateRouteService],
    canDeactivate: [TranslateRouteService]
  }
];

data.i18n - url path to folder of translations .json files
canActivate- Trigger preload of translation block by data.i18n property
canDeactive - Optional: depends from our business logic: is we need to store or clear鈥搖p translation partial block after component destroyed

TranslateRouteService

import {Component, Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {ActivatedRouteSnapshot, CanActivate, CanDeactivate, RouterStateSnapshot} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {Observable} from 'rxjs/Observable';

export const i18nPrefix = 'i18n/';
export const i18nSuffix = '.json';

@Injectable()
export class TranslateRouteService implements CanActivate, CanDeactivate<Component> {

  /**
   * Store keys, that already loaded
   */
  private storage: Set<string>;

  constructor(private i18n: TranslateService, private http: Http) {
    this.storage = new Set();
  }

  /**
   * Partial load block of translation
   * @param {ActivatedRouteSnapshot} route
   * @param {RouterStateSnapshot} state
   * @returns {Observable<boolean>}
   */
  canActivate(route: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean>
    | Promise<boolean>
    | boolean {

    const path = route.data['i18n'];

    // No i18n data path provided
    if (!path) {
      return true;
    }

    // Already stored
    if (this.storage.has(path)) {
      return true;
    }

    const url = i18nPrefix + path + '/' + this.i18n.getDefaultLang() + i18nSuffix;

    return this.http.get(url).map((response: Response) => {

      this.addPartialBlock(path, response.json());

      return true;

    }).catch(() => {

      // Http response problem, i18n path file not found
      return Observable.of(true);

    });

  }

  /**
   * Clear partial block of translation
   * @param {Component} component
   * @param {ActivatedRouteSnapshot} route
   * @returns {Observable<boolean>}
   */
  canDeactivate(component: Component, route: ActivatedRouteSnapshot): Observable<boolean>
    | Promise<boolean>
    | boolean {

    const path = route.data['i18n'];

    // No i18n data path provided
    if (!path) {
      return true;
    }

    this.removePartialBlock(path);

    return true;
  }

  /**
   * Append to object
   * @param {string} key
   * @param {Object} data
   */
  private addPartialBlock(key: string, data?: Object): void {

    this.i18n.setTranslation(
      this.i18n.getDefaultLang(),
      {[key]: data},
      true
    );

    this.storage.add(key);

  }

  /**
   * Set to undefined block of translation, by key
   * @param {string} key
   */
  private removePartialBlock(key: string): void {
    this.addPartialBlock(key);
    this.storage.delete(key);
  }

}

account.component.html

<span> Hello {{ 'account.hello' | translate }} </span>

I hope this helps.

All 8 comments

Hi

I have the same issue in lazy load architecture. Problem that current release don't support partial loading from the box. My solution to handle this problem and to keep standard way of using angular + ngx-translate is next:

Structure

/src
- - /app
- - /i18n
- - - - /account
- - - - - - en.json

account/en.json

{
  "hello": "world"
}

SharedModule

imports/exports: TranslateModule

Routes

const routes: Routes = [
{
    path: 'account',
    loadChildren: './account/account.module#AccountModule',
    data: {
      i18n: 'account'
    },
    canActivate: [TranslateRouteService],
    canDeactivate: [TranslateRouteService]
  }
];

data.i18n - url path to folder of translations .json files
canActivate- Trigger preload of translation block by data.i18n property
canDeactive - Optional: depends from our business logic: is we need to store or clear鈥搖p translation partial block after component destroyed

TranslateRouteService

import {Component, Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {ActivatedRouteSnapshot, CanActivate, CanDeactivate, RouterStateSnapshot} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {Observable} from 'rxjs/Observable';

export const i18nPrefix = 'i18n/';
export const i18nSuffix = '.json';

@Injectable()
export class TranslateRouteService implements CanActivate, CanDeactivate<Component> {

  /**
   * Store keys, that already loaded
   */
  private storage: Set<string>;

  constructor(private i18n: TranslateService, private http: Http) {
    this.storage = new Set();
  }

  /**
   * Partial load block of translation
   * @param {ActivatedRouteSnapshot} route
   * @param {RouterStateSnapshot} state
   * @returns {Observable<boolean>}
   */
  canActivate(route: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean>
    | Promise<boolean>
    | boolean {

    const path = route.data['i18n'];

    // No i18n data path provided
    if (!path) {
      return true;
    }

    // Already stored
    if (this.storage.has(path)) {
      return true;
    }

    const url = i18nPrefix + path + '/' + this.i18n.getDefaultLang() + i18nSuffix;

    return this.http.get(url).map((response: Response) => {

      this.addPartialBlock(path, response.json());

      return true;

    }).catch(() => {

      // Http response problem, i18n path file not found
      return Observable.of(true);

    });

  }

  /**
   * Clear partial block of translation
   * @param {Component} component
   * @param {ActivatedRouteSnapshot} route
   * @returns {Observable<boolean>}
   */
  canDeactivate(component: Component, route: ActivatedRouteSnapshot): Observable<boolean>
    | Promise<boolean>
    | boolean {

    const path = route.data['i18n'];

    // No i18n data path provided
    if (!path) {
      return true;
    }

    this.removePartialBlock(path);

    return true;
  }

  /**
   * Append to object
   * @param {string} key
   * @param {Object} data
   */
  private addPartialBlock(key: string, data?: Object): void {

    this.i18n.setTranslation(
      this.i18n.getDefaultLang(),
      {[key]: data},
      true
    );

    this.storage.add(key);

  }

  /**
   * Set to undefined block of translation, by key
   * @param {string} key
   */
  private removePartialBlock(key: string): void {
    this.addPartialBlock(key);
    this.storage.delete(key);
  }

}

account.component.html

<span> Hello {{ 'account.hello' | translate }} </span>

I hope this helps.

@denirun Very good but doesn't work when you change the language?

@Tuizi you can just call setDefaultLang() method of TranslateService, and your project start using this default lang. But I'm found small problem there: your current values on the screen not refreshed automatically, you need some workaround. To refresh translate filters.
I personally solve this problem next:
1. If my project is HUGE: I'm using lazy partial loading with this technique, and make hard鈥搑eload of page, when user change locale. Because I'm need to setup {LOCALE_ID}, you can find more info here:
https://github.com/angular/angular-cli/issues/6683
But anyway, you can solve without hard-reload, by calling methods from TranslateService resetLang, use, setDefaultLang and don't using standard LOCALE_ID by rewriting your App with custom logic.

2. If my project is TINY: I'll using standard angular i18n library

I wrote a article about how to have 1 json file per lazy loaded module without having to write a new Custom Loader etc... it's quiet simple, only the documentation is not clear in fact:
https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f

I modified @denirun's code to allow for multiple json files for one module:

  canActivate(route: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean>
    | Promise<boolean>
    | boolean {

    const paths = Array.isArray(route.data['i18n']) ? route.data['i18n'] : [ route.data['i18n'] ];

    // No i18n data path provided
    if (!paths || !paths.length) {
      return true;
    }

    let i18nRequests = paths.map(path => {

      // Already stored
      if (this.storage.has(path + '/' + this.translate.currentLang)) {
        return Observable.of(true);
      }

      let url = i18nPrefix + path + '/' + this.translate.currentLang + i18nSuffix;

      return this.http.get(url).map((response: Response) => {
        return this.addPartialBlock(path, response.json());

      }).catch(() => {

        // Http response problem, i18n path file not found
        return Observable.of(true);

      });

    });

    return Observable.forkJoin(i18nRequests).map(res => true);

  }

Routing:

data: {
  i18n: [ 'feature', 'shared' ],
},

...

data: {
  i18n: 'feature',
},

Hello, I'm closing this issue because it's too old.
If you have a similar problem with recent version of the library, please open a new issue.

@ocombe I think that is a good point to have a method to load partially the keys (for our projects it's a must). If you want I can open another issue, but what do you think about this?

Yes, open a feature request for that, just check if there isn't another one already (I think there is one)

Was this page helpful?
0 / 5 - 0 ratings