Search code examples
spartacus-storefront

Getting language from browser doesn't work with SSR


I have a website with multi language ['de', 'fr', 'it', 'en']

Current behaviour:

When I enter example.com without a previous session I am redirected to example.com/de (first value in the array)

Wanted behaviour:

I want to be redirected to the browser's language I have (in case there is none in session)

I have extended the service LanguageService to override the initialize() function as follows:

initialize(): void {
    let value;
    this.getActive()
        .subscribe((val) => (value = val))
        .unsubscribe();
    if (value) {
        // don't initialize, if there is already a value (i.e. retrieved from route or transferred from SSR)
        return;
    }


    const languages = this.getLanguages();
    const sessionLanguage = this.sessionStorageCustom && this.sessionStorageCustom.getItem('language');

    if (sessionLanguage && languages?.includes(sessionLanguage)) {
        this.setActive(sessionLanguage);
    } else {
        const browserLanguage = this.getBrowserLanguage();
        if (browserLanguage && languages?.includes(browserLanguage)) {
            this.setActive(browserLanguage);
        }
    }
}

Helpers:

private getLanguages(): string[] | null {
    let languages = this.siteContextParamsService.getParamValues('language');

    // Removing English from options
    languages = languages.filter((l) => !(l.toLowerCase() === 'en'));

    if (languages) return languages;
    return null;
}

private getBrowserLanguage(): string | null {
    let language = this.winRef.nativeWindow?.navigator.language;
    if (language) {
        language = language.slice(0, 2);
        return language;
    }
    return null;
}

Constructor:

private sessionStorageCustom: Storage | undefined;

constructor(
    protected store: Store<StateWithSiteContext>,
    protected winRef: WindowRef,
    protected config: SiteContextConfig,
    protected siteContextParamsService: SiteContextParamsService
) {
    super(store, winRef, config);

    // cannot use default variable because it's private
    this.sessionStorageCustom = winRef.sessionStorage;
}

On CSR everything works as expected but when in SSR I always go to the default language.

(Because on server side there is no browser's language. I assume.)

How can I force this code be executed at the client side? or what can I do to accomplish this?


Solution

  • By default the Spartacus siteContext will always default to the SSR transfer state if it is present before running the browser language logic.

    I can see two solutions you could try:

    1. You can remove the SSR transfer state. This way Spartacus will run your logic in browser every time. You can do it with:
          state: {
            ssrTransfer: {
              keys: { [SITE_CONTEXT_FEATURE]: false },
            },
          },
    

    This solution is not ideal because the SSR page will contain the default falback language which might not be the one the user is using so the page might flicker.

    1. You can add custom logic in the LanguageService that will run only in the server and use the Accept-Language header to set the language. This header is set by the browser to let the server know what language the user wants. You can read this article which provides a great example on how to use this mechanism in Angular. The example is not in Spartacus but the same logic can be used.

    One final thought, the Spartacus siteContext persistence will be updated in 4.0 to use the global state persistence mechanism.