robisim74 / angular-l10n

Angular library to translate texts, dates and numbers
MIT License
380 stars 59 forks source link

Not loading translations #320

Closed othenos closed 3 years ago

othenos commented 3 years ago

This just started with Angular 10 and 11 (this particular version is 11.0.1). It runs fine in development mode but when running in production mode the translations are not getting loaded. I think the basic indicator is when subscribing to translation.onError an error is displayed "Translation Error TypeError: n.handle(...).do is not a function". Extremely difficult to debug in production mode with no ability to set breakpoints in the code. I hope I am providing enough info. If not, let me know. Thanks for your help.

import { Injectable, Inject, Optional } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';

import {
    L10nConfig,
    L10nLoader,
    L10nStorage,
    L10nLocale,
    L10nTranslationLoader,
    L10nProvider,
    L10nValidation,
    L10N_LOCALE,
    L10nNumberFormatOptions,
    L10nDateTimeFormatOptions,
    parseDigits, L10nUserLanguage
} from 'angular-l10n';

export const l10nConfig: L10nConfig = {
    format: 'language-region',
    providers: [
        { name: 'app', asset: './assets/i18n/app', options: { version: '9.0.0' } }
    ],
    fallback: false,
    cache: true,
    keySeparator: '.',
    defaultLocale: { language: 'en-US', currency: 'USD' },
    schema: [
        { locale: { language: 'en-US', currency: 'USD' }, dir: 'ltr', text: 'United States' },
    ],
    defaultRouting: true
};

export function initL10n(l10nLoader: L10nLoader): () => Promise<void> {
    return () => l10nLoader.init();
}

@Injectable() export class AppStorage implements L10nStorage {

    private hasStorage: boolean;

    constructor() {
        this.hasStorage = typeof Storage !== 'undefined';
    }

    public async read(): Promise<L10nLocale | null> {
        console.log('AppStorage read');
        if (this.hasStorage) {
            console.log('AppStorage has storage');
            return Promise.resolve(JSON.parse(sessionStorage.getItem('locale')));
        }
        return Promise.resolve(null);
    }

    public async write(locale: L10nLocale): Promise<void> {
        console.log('AppStorage write');
        if (this.hasStorage) {
            sessionStorage.setItem('locale', JSON.stringify(locale));
        }
    }

}

@Injectable() export class HttpTranslationLoader implements L10nTranslationLoader {

    private headers = new HttpHeaders({ 'Content-Type': 'application/json' });

    constructor(@Optional() private http: HttpClient) { }

    public get(language: string, provider: L10nProvider): Observable<{ [key: string]: any }> {
        console.log('HttpTranslationLoader');
        const url = `${provider.asset}-${language}.json`;
        const options = {
            headers: this.headers,
            params: new HttpParams().set('v', provider.options.version)
        };
        return this.http.get(url, options);
    }

}

@Injectable() export class LocaleValidation implements L10nValidation {

    constructor(@Inject(L10N_LOCALE) private locale: L10nLocale) { }

    public parseNumber(value: string, options?: L10nNumberFormatOptions, language = this.locale.language): number | null {
        if (value === '' || value == null) { return null };

        let format = { minimumIntegerDigits: 1, minimumFractionDigits: 0, maximumFractionDigits: 0 };
        if (options && options.digits) {
            format = { ...format, ...parseDigits(options.digits) };
        }

        let decimalSeparator: string;
        switch (language) {
            case 'it-IT':
                decimalSeparator = ',';
                break;
            default:
                decimalSeparator = '.';
        }

        const pattern = `^-?[\\d]{${format.minimumIntegerDigits},}(\\${decimalSeparator}[\\d]{${format.minimumFractionDigits},${format.maximumFractionDigits}})?$`;
        const regex = new RegExp(pattern);
        return regex.test(value) ? parseFloat(value.replace(decimalSeparator, '.')) : null;
    }

    public parseDate(value: string, options?: L10nDateTimeFormatOptions, language = this.locale.language): Date | null {
        return null;
    }

}
@Injectable() export class UserLanguage implements L10nUserLanguage {

    public get(): Promise<string | null> {
        let browserLanguage = null;
        if (navigator !== undefined && navigator.language) {
            // Takes the complete locale, not only the language as in the the default version
            console.log('UserLanguage ' + navigator.language);
            browserLanguage = navigator.language;
        }
        return Promise.resolve(browserLanguage);
    }
}

app.module code imports

        L10nTranslationModule.forRoot(
            l10nConfig,
            {
                storage: AppStorage,
                translationLoader: HttpTranslationLoader,
                userLanguage: UserLanguage,
            }
        ),
        L10nIntlModule,
        L10nValidationModule.forRoot({validation: LocaleValidation}),
        L10nRoutingModule.forRoot(),

app.module code providers

        {
            provide: APP_INITIALIZER,
            useFactory: initL10n,
            deps: [L10nLoader],
            multi: true
        },

app.component

    constructor(private authentication: AuthenticationService,
                private backendService: BackendService,
                private authenticationRoutingService: AuthenticationRoutingService, /* This is only here to cause the service to be loaded immediately on startup */
                private globalErrorHandlerService: GlobalErrorHandlerService,
                public router: Router,
                public route: ActivatedRoute,
                public googleAnalyticsEventsService: GoogleAnalyticsEventsService,
                public dialog: MatDialog,
                public orderService: OrderService,
                private cdRef: ChangeDetectorRef,
                @Inject(L10N_LOCALE) public locale: L10nLocale,
                @Inject(L10N_CONFIG) private l10nConfig: L10nConfig,
                private translation: L10nTranslationService) {
        this.currentUrl = '/landing';
        console.log('subscribing to router events');
        this.router.events.subscribe(event => {
            if (event instanceof NavigationEnd) {
                gtag('config', 'UA-175223990-1',
                    {
                        'page_path': event.urlAfterRedirects
                    }
                );
            }
        });
        // this.translation.onChange().subscribe({
        //     next: (locale1: L10nLocale) => {
        //         console.log(locale1);
        //         console.log(this.translation.data);
        //     }
        // });
        this.translation.onError().subscribe({
            next: (error: any) => {
                if (error) { console.log('Translation Error ' + error); }
            }
        });
        console.log('generating new order');
        this.orderService.generateNewOrder();
    }

Firefox console log

AppStorage read main.1f52d5bd9627c2718941.js:1:880167
AppStorage has storage main.1f52d5bd9627c2718941.js:1:880215
HttpTranslationLoader main.1f52d5bd9627c2718941.js:1:880665
intercept - return do main.1f52d5bd9627c2718941.js:1:879395
subscribing to router events main.1f52d5bd9627c2718941.js:1:869855
Translation Error TypeError: n.handle(...).do is not a function main.1f52d5bd9627c2718941.js:1:870065
generating new order main.1f52d5bd9627c2718941.js:1:870104
GEThttp://localhost:4200/assets/images/cropped_logo.jpg
[HTTP/1.1 304 Not Modified 1ms]

Object { language: "" }
main.1f52d5bd9627c2718941.js:1:870557
Object {  }
main.1f52d5bd9627c2718941.js:1:870572
TypeError: n.handle(...).do is not a function
    intercept http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    handle http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    intercept http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    handle http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    handle http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    r http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _tryNext http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _next http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    next http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    i http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _trySubscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    call http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    call http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    call http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    r http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    l http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _innerSub http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _tryNext http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _next http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    next http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    i http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    _trySubscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    call http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    subscribe http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    loadTranslation http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    loadTranslation http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    Rb http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    Rb http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    loadTranslation http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    init http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    o http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invoke http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    onInvoke http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invoke http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    run http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    P http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    invokeTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    onInvokeTask http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invokeTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    runTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    m http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    promise callback*k http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    scheduleTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    onScheduleTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    scheduleTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    scheduleTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    scheduleMicroTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    P http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    Z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    w http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    promise callback*N/e.prototype.then/< http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    then http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    Z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    w http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    l http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    z http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    l http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    o http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invoke http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    onInvoke http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invoke http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    run http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    P http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    invokeTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    onInvokeTask http://localhost:4200/main.1f52d5bd9627c2718941.js:1
    invokeTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    runTask http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
    m http://localhost:4200/polyfills.f0fbbb0e666dfb2508c2.js:1
robisim74 commented 3 years ago

@mike-robinson The error appears to be due to an interceptor (see SO questions https://stackoverflow.com/search?q=next.handle%28%E2%80%A6%29.do+is+not+a+function), but this library does not use an interceptor or a do operator: it only fires the errors on http requests.

othenos commented 3 years ago

Thanks so much for pointing me in the right direction. And yes, it was definitely the httpInterceptor code.