intershop / intershop-pwa

The Intershop PWA is an Angular based progressive web app storefront for the Intershop Commerce Platform.
https://www.intershop.com/progressive-web-app
MIT License
159 stars 85 forks source link

Node.js exits with error after adding new locale to PWA #1646

Closed gorpet closed 6 months ago

gorpet commented 6 months ago

After adding new locale to PWA and backend and try to switch to new locale, error 'no elements in sequence' is thrown and Node.js processes exits.

Actual Behavior

Error 'no elements in sequence' and Node.js process exits

Expected Behavior

Page is loaded with configured and selected locale (e.g. nl_NL)

Steps to Reproduce the Bug

Steps to reproduce the behavior:

  1. Go to SLDSystem and add new locale to PWA application e.g. nl_NL
  2. Follow steps to add new locale e.g. nl_NL as described here https://github.com/intershop/intershop-pwa/blob/develop/docs/concepts/configuration.md#extend-locales
  3. Open PWA frontpage and switch to new locale nl_NL using language selector
  4. See error printing in PWA log, and also Node.js process is killed

Environment Details

Latest PWA 5.1.0 ICM 7.10.41.0 or any version, also this Mock REST response for /configuration endpoint can be used:

  "data": {
    "application": {
      "id": "application",
      "applicationId": null,
      "applicationType": "intershop.REST",
      "displayName": null,
      "urlIdentifier": "-",
      "default": true
    },
    "basket": {
      "id": "basket",
      "expirationType": "Time",
      "lineItemPositionHandling": "AssignOnly",
      "addProductBehaviour": "MergeQuantities",
      "lifetime": "28800",
      "maxItemSize": "50",
      "maxItemQuantity": "100",
      "minTotalValue": null,
      "maxTotalValue": null,
      "acceleration": true,
      "termsAndConditions": true,
      "emailOptIn": false,
      "displayTaxesAndFees": "ConsolidatedTaxes",
      "desiredDeliveryDate": true,
      "deliveryExcludeSaturday": true,
      "deliveryExcludeSunday": true,
      "packSlipMessage": true,
      "packSlipMessageMaxLength": "1000",
      "giftWrap": false,
      "giftMessage": false,
      "giftMessageMaxLength": null,
      "partialOrderNo": true,
      "customerProductID": true
    },
    "captcha": {
      "id": "captcha",
      "redemptionOfGiftCardsAndCertificates": true,
      "forgotPassword": true,
      "contactUs": true,
      "emailShoppingCart": true,
      "register": true
    },
    "customFieldDefinitions": [
      {
        "name": "commissionNumber",
        "type": "String",
        "description": "Commission number of the line item.",
        "displayName": "Commission number",
        "position": 1,
        "scopes": [
          {
            "name": "BasketLineItem",
            "isEditable": true,
            "isVisible": true
          },
          {
            "name": "OrderLineItem",
            "isEditable": false,
            "isVisible": true
          }
        ]
      },
      {
        "name": "projectNumber",
        "type": "String",
        "description": "Unique identifier to associate the order with a project.",
        "displayName": "Project Number",
        "position": 2,
        "scopes": [
          {
            "name": "Order",
            "isEditable": false,
            "isVisible": true
          },
          {
            "name": "Basket",
            "isEditable": true,
            "isVisible": true
          }
        ]
      }
    ],
    "general": {
      "id": "general",
      "defaultCurrency": "USD",
      "defaultLocale": "en_US",
      "currencies": [
        "USD",
        "EUR"
      ],
      "locales": [
        "de_DE",
        "en_US",
        "fr_FR",
        "nl_NL"
      ],
      "customerTypeForLoginApproval": []
    },
    "marketing": {
      "id": "marketing",
      "newsletterSubscriptionEnabled": true
    },
    "preferences": {
      "id": "preferences",
      "ChannelPreferences": {
        "id": "ChannelPreferences",
        "EnableAdvancedVariationHandling": "false",
        "PasswordRetrievalEmailSubject": "Password Retrieval",
        "ContactFormUserServiceEmailFrom": "info@test.intershop.de",
        "PasswordReminderEmailFrom": "info@test.intershop.de"
      },
      "ShippingPreferences": {
        "id": "ShippingPreferences",
        "MultipleShipmentsSupported": "true"
      },
      "UserCredentialPreferences": {
        "id": "UserCredentialPreferences",
        "UserRegistrationLoginType": "email"
      }
    },
    "pricing": {
      "id": "pricing",
      "priceType": "net",
      "privateCustomerPriceDisplayType": "gross",
      "smbCustomerPriceDisplayType": "net",
      "defaultCustomerTypeForPriceDisplay": "SMB"
    },
    "services": {
      "id": "services",
      "Address_Check_Services": {
        "id": "Address_Check_Services",
        "runnable": "true"
      },
      "ReCaptchaV3ServiceDefinition": {
        "id": "ReCaptchaV3ServiceDefinition",
        "runnable": "true",
        "ScoreThreshold": "0.5",
        "VerificationURL": "https://www.google.com/recaptcha/api/siteverify",
        "SiteKey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
      },
      "OrderApprovalServiceDefinition": {
        "id": "OrderApprovalServiceDefinition",
        "runnable": "true"
      }
    },
    "shipping": {
      "deliveryExcludeSaturday": true,
      "deliveryExcludeSunday": true,
      "desiredDeliveryDate": true,
      "desiredDeliveryDaysMax": "90",
      "desiredDeliveryDaysMin": "2",
      "id": "shipping",
      "messageToMerchant": true,
      "multipleShipmentsSupported": true,
      "pickupInStoreEnabled": false,
      "readyForShipmentMaximumTime": "7",
      "readyForShipmentMinimumTime": "3"
    }
  }
}

Additional Context, like Screenshots, Log File Snippets etc.

image

import localeDe from '@angular/common/locales/de';
import localeFr from '@angular/common/locales/fr';
import localeNL from '@angular/common/locales/nl';
import { Inject, LOCALE_ID, NgModule, TransferState } from '@angular/core';
import {
  MissingTranslationHandler,
  TranslateCompiler,
  TranslateLoader,
  TranslateModule,
  TranslateService,
} from '@ngx-translate/core';

import { SSR_LOCALE } from './configurations/state-keys';
import {
  FALLBACK_LANG,
  FallbackMissingTranslationHandler,
} from './utils/translate/fallback-missing-translation-handler';
import { ICMTranslateLoader, LOCAL_TRANSLATIONS } from './utils/translate/icm-translate-loader';
import { PWATranslateCompiler } from './utils/translate/pwa-translate-compiler';
import { TranslationGenerator } from './utils/translate/translations-generator';

@NgModule({
  imports: [
    TranslateModule.forRoot({
      loader: { provide: TranslateLoader, useClass: ICMTranslateLoader },
      missingTranslationHandler: { provide: MissingTranslationHandler, useClass: FallbackMissingTranslationHandler },
      useDefaultLang: false,
      compiler: { provide: TranslateCompiler, useClass: PWATranslateCompiler },
    }),
  ],
  providers: [
    { provide: FALLBACK_LANG, useValue: 'en_US' },
    {
      provide: LOCAL_TRANSLATIONS,
      useValue: {
        useFactory: (lang: string) => {
          switch (lang) {
            case 'en_US':
              return import('../../assets/i18n/en_US.json');
            case 'fr_FR':
              return import('../../assets/i18n/fr_FR.json');
            case 'de_DE':
              return import('../../assets/i18n/de_DE.json');
            case 'nl_NL':
              return import('../../assets/i18n/nl_NL.json');
          }
        },
      },
    },
    TranslationGenerator,
  ],
})
export class InternationalizationModule {
  constructor(
    @Inject(LOCALE_ID) angularDefaultLocale: string,
    translateService: TranslateService,
    transferState: TransferState,
    generator: TranslationGenerator
  ) {
    registerLocaleData(localeDe);
    registerLocaleData(localeFr);
    registerLocaleData(localeNL);

    let defaultLang = angularDefaultLocale.replace(/\-/, '_');
    if (transferState.hasKey(SSR_LOCALE)) {
      defaultLang = transferState.get(SSR_LOCALE, defaultLang);
    }
    translateService.setDefaultLang(defaultLang);
    translateService.use(defaultLang);

    generator.init();
  }
}

AB#96210

andreassteinmann commented 6 months ago

@gorpet

Hi,

I used the following setup but the mentioned error cannot be reproduced:

I'm able to switch to nl_NL and all errors in the console are related to REST requests that return a 500 status because the referenced ICM server does not have product or categories data for nl_NL.

Remarks:

gorpet commented 6 months ago

Hi @andreassteinmann Both internationalization.module.ts and /configurations are correct, there was just c/p issue. The only difference here is that I start server without docker and with command: TRUST_ICM=true LOGGING=true npm run start:ssr-dev -- --ssl --host dds-x1e-120.local --port 443 Can you try that? Thanks for support, Goran

andreassteinmann commented 6 months ago

Hi @gorpet I tried it using TRUST_ICM=true LOGGING=true npm run start:ssr-dev -- --ssl --port 443 and I also do not get the error. The mocked REST call is working and includes the mentioned languages.

I'm still trying to figure out what might cause the issue.

andreassteinmann commented 6 months ago

Hi @gorpet,

I created a temporary branch https://github.com/intershop/intershop-pwa/tree/tmp/new-locale-nl_NL with the changes I made for the new locale nl_NL so you can have a look at it and test it on your side. I used

Changes:

andreassteinmann commented 6 months ago

If the described error persists, you can try to change the code in src\app\core\utils\translate\icm-translate-loader.ts to use take(1) instead of first() from

        ofType(routerNavigationAction),
        first(),
        switchMap(() =>
          this.localizations.getServerTranslations(lang).pipe(
            tap(data => {
              this.transferState.set(SSR_TRANSLATIONS, data);
            })
          )
        ),

to

        ofType(routerNavigationAction),
        take(1),
        switchMap(() =>
          this.localizations.getServerTranslations(lang).pipe(
            tap(data => {
              this.transferState.set(SSR_TRANSLATIONS, data);
            })
          )
        ),
gorpet commented 6 months ago

Hi @andreassteinmann Thank you for your support, I just tried running branch 'new-locale-nl_NL' with mock data provided and it seems it works. Then I removed mock data to try with my local server, and error occurs. After that I copied/pasted server response from /configuration REST call to get.json file and reenabled mock in environment and then it works again without error... After that I changed back to real server call and changed icm-translate-loader.ts as you suggested and then it works! Not sure why, do you have idea why real call with take(1) works and with first() it does not? Also what is strange is that exact mocked response work either way...

Thank you again and kind regards, Goran

andreassteinmann commented 6 months ago

@gorpet first() expects the source Observable to emit at least one item. If the Observable completes without any emissions, first() will throw a NoSuchElementException to indicate that no elements were emitted. In the context of the issue, if the Observable is expected to emit at least one value but doesn't, first() enforces the emission of at least one item. This could happen if, for example, the translation data isn't loaded as expected due to a network issue, incorrect configuration, or if the data is filtered out before it reaches first().

Maybe we need to change it in the code as well. I need to check this with my colleagues. But until then, you may work with the code changes to take(1).

Kind regards, Andreas

gorpet commented 6 months ago

Hi @andreassteinmann, Ok, then until you change it in code, I will override icm-translate-loader.ts and use take(1).

Kind regards, Goran

andreassteinmann commented 6 months ago

Hi @gorpet, I'm going to delete the temp branch this afternoon. In case you need to keep the changes, just create a backup ;)

andreassteinmann commented 6 months ago

The PR https://github.com/intershop/intershop-pwa/pull/1652 is created.