angularfrontendtranslationngx-translate

No query params in custom translation loader in Angular


Im currently work on app where we need to use different branding and translations. For that, I need to get some information from the query params and this is working fine for getting all assets (images/icons/logos) which is done during ngOnInit() but I have problem with translations. I created custom translation loader which looks like this:

export class TranslationFileLoader implements TranslateLoader {
  private tenant: string | null = "";
  private sp: string | null = "";  

  constructor(private http: HttpClient, private actRoute: ActivatedRoute) {
     this.actRoute.queryParams.subscribe(params => {
       if (params !== null) {
         this.tenant = params["tenant"];
         this.sp = params["sp"];
       }
     });    
  }

  getTranslation(lang: string):  Observable<any> {        
      return this.http.get(environment.blobUrl + "/" + this.tenant.replace(/[.]/g,'')+"/i18n/" + this.sp?.toLowerCase() + "/" + `${lang}` + ".json");
    }
  }

but when the loader is intialized, query params are empty and then link for translation contain undefined. Is there a way to somehow get those params or maybe do it differently? I thought about set those two variables in localStorage.


Solution

  • The problem was using useFactory instead we should use useClass and we should point to the class we created TranslationFileLoader, then since activated route is always empty, we can use native javascipt query params parsing to solve the issue.

    ...
    const searchParams = window?.location?.href?.split('?')?.[1];
    const urlParams = new URLSearchParams(
      searchParams ? `?${searchParams}` : ''
    );
    console.log(urlParams.get('tenant'));
    console.log(urlParams.get('sp'));
    // if no data then it will fallback to the default values you set using `||` condition in javascript since lhs is false!
    this.tenant = urlParams.get('tenant') || 'default'; // you can also give fallback data by giving: params?.["tenant"] || 'asdf';
    this.sp = urlParams.get('sp') || 'en'; // you can also give fallback data by giving: params?.["tenant"] || 'en';
    ...
    

    Below is a working example for your reference!

    import { Component, importProvidersFrom } from '@angular/core';
    import { TranslateService } from '@ngx-translate/core';
    import { CommonModule } from '@angular/common';
    import { ActivatedRoute, provideRouter } from '@angular/router';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { AppModule } from './app/app.module';
    import { BrowserModule } from '@angular/platform-browser';
    import {
      HttpClient,
      HttpClientModule,
      provideHttpClient,
    } from '@angular/common/http';
    import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
    import { TranslateHttpLoader } from '@ngx-translate/http-loader';
    import 'zone.js';
    import { Observable } from 'rxjs';
    
    export class TranslationFileLoader implements TranslateLoader {
      private tenant: string | null = '';
      private sp: string | null = '';
    
      constructor(private http: HttpClient) {}
    
      getTranslation(lang: string): Observable<any> {
        const searchParams = window.location.href.split('?')[1];
        const urlParams = new URLSearchParams(
          searchParams ? `?${searchParams}` : ''
        );
        console.log(urlParams.get('tenant'));
        console.log(urlParams.get('sp'));
        // if no data then it will fallback to the default values you set using `||` condition in javascript since lhs is false!
        this.tenant = urlParams.get('tenant') || 'default'; // you can also give fallback data by giving: params?.["tenant"] || 'asdf';
        this.sp = urlParams.get('sp') || 'en'; // you can also give fallback data by giving: params?.["tenant"] || 'en';
        return this.http.get(
          'https://google.com' +
            '/' +
            this.tenant.replace(/[.]/g, '') +
            '/i18n/' +
            this.sp?.toLowerCase() +
            '/' +
            `${lang}` +
            '.json'
        );
      }
    }
    
    @Component({
      selector: 'app-root',
      imports: [CommonModule, TranslateModule],
      standalone: true,
      template: `
        <div>
          <h2>{{ 'HOME.TITLE' | translate }}</h2>
          <label>
            {{ 'HOME.SELECT' | translate }}
            <select #langSelect (change)="translate.use(langSelect.value)">
              <option *ngFor="let lang of translate.getLangs()" [value]="lang" [selected]="lang === translate.currentLang">{{ lang }}</option>
            </select>
          </label>
        </div>
      `,
    })
    export class AppComponent {
      constructor(public translate: TranslateService) {
        translate.addLangs(['en', 'fr']);
        translate.setDefaultLang('en');
    
        const browserLang = translate.getBrowserLang();
        translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
      }
    }
    
    bootstrapApplication(AppComponent, {
      providers: [
        provideHttpClient(),
        provideRouter([]),
        importProvidersFrom(
          TranslateModule.forRoot({
            loader: {
              provide: TranslateLoader,
              useClass: TranslationFileLoader,
              deps: [HttpClient],
            },
            useDefaultLang: false,
          })
        ),
      ],
    });
    

    Stackblitz Demo

    Try modifying the code so that first we access the route snapshot (no need to subscribe!) and get the data, then we return the API call!

    Because this is a case of a timing issue, the subscribe executes after the getTranslation method is called, hence you might get this problem!

    export class TranslationFileLoader implements TranslateLoader {
      private tenant: string | null = "";
      private sp: string | null = "";  
    
      constructor(private http: HttpClient, private actRoute: ActivatedRoute) {
      }
    
      getTranslation(lang: string):  Observable<any> {  
          const params = this.actRoute?.snapshot?.params;
          // if no data then it will fallback to the default values you set using `||` condition in javascript since lhs is false!
          this.tenant = params?.["tenant"]; // you can also give fallback data by giving: params?.["tenant"] || 'asdf';
          this.sp = params?.["sp"]; // you can also give fallback data by giving: params?.["tenant"] || 'en';
          return this.http.get(environment.blobUrl + "/" + this.tenant.replace(/[.]/g,'')+"/i18n/" + this.sp?.toLowerCase() + "/" + `${lang}` + ".json");
        }
      }
      ...