Repository navigation
Pass an explicit language to cart warnings and loyalty point messages - #938
Merged
KrzysztofPajak merged 6 commits intoOct 17, 2026
Merged
Conversation
added 2 commits
October 17, 2026 15:45
Awarding, reducing and returning loyalty points for an order wrote the history message in the working language of whatever request triggered it. From a scheduled task, an admin action or the API that was the admin's language or nothing at all. The message now uses the language the customer placed the order in (Order.CustomerLanguageId).
The cart validators and the loyalty points for registration read the working language of the current request through GetResource(name), so outside a storefront request (scheduled tasks, the API, background handlers) their messages came out empty or in the wrong language. - IShoppingCartValidator methods and the validator records take the language the warnings are written in. - IShoppingCartService.AddToCart, UpdateShoppingCartItem and MigrateShoppingCart, AddRequiredProductsCommand and ReOrderCommand carry it down to the validators. - CustomerLoggedInEvent and CustomerRegisteredEvent carry the working language of the request; IExternalAuthenticationService.Authenticate and IOidcCustomerResolver.ResolveForLogin take it from the controllers. A customer registered from an access token has none, and the registration loyalty points fall back to the store's default language. - EndAuctionsTask validates the winning bid in the admin language, since its warnings only reach the log. No GetResource(name) call is left in src/Business.
added 4 commits
October 17, 2026 15:47
An order without CustomerLanguageId, a deleted order or store language, or a store without a published language made GetResource throw on a null key or write the raw resource key. GetLanguageOrStoreDefault picks the given language while it exists, then the store default, then any store language.
Plugins that add cart warnings now get the language the core warnings are written in.
…pingCartItem A required parameter inserted before the optional ones let an old positional null bind to the language and fail at runtime; now an old call does not compile. EndAuctionsTask skips a bid instead of throwing when no language exists at all.
KrzysztofPajak
deleted the
refactor/business-explicit-context-translations
branch
October 17, 2026 15:00
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the explicit-context work in Business (after #930, #931, #932, #936, #937)
Type: feature
Issue
57 calls in
src/Businesstranslated withITranslationService.GetResource(name), which reads the working language of the current request. These were the 11 cart validators inGrand.Business.Checkout/Validators, the three loyalty point handlers for orders, and the loyalty points for registration. Outside a storefront request the text was wrong or missing. With no request (a scheduled task, a background handler)GetResource(name)returns an empty string. From the admin panel or the API it uses the admin's or caller's language, not the customer's.Examples:
EndAuctionsTaskvalidated the winning bid with empty warning texts, so the log line said nothing.Solution
IShoppingCartValidatormethods and the validator records take aLanguage. The validators callGetResource(name, language.Id).IShoppingCartService.AddToCart,UpdateShoppingCartItemandMigrateShoppingCart,AddRequiredProductsCommandandReOrderCommandpass the language down. Storefront and admin callers passWorkContext.WorkingLanguage.PlaceOrderCommandHandleruses the language the order is placed in (PlaceOrderContainer.WorkingLanguage).EndAuctionsTaskuses the admin language (LanguageSettings.DefaultAdminLanguageId), because its warnings only reach the log.CustomerLoggedInEventandCustomerRegisteredEventcarry the working language of the request. The login event's language goes to the cart merge, the registration event's language to the registration loyalty points.IExternalAuthenticationService.AuthenticateandIOidcCustomerResolver.ResolveForLogintake it from the controllers (Facebook, Google, OpenID Connect). An account created from an API access token (OidcCustomerResolver.Resolve) has no request language. The registration handler then uses the store's default language, or the first language of the store.Order.CustomerLanguageId, the language the customer placed the order in. This is a separate commit.ILanguageService.GetLanguageOrStoreDefault(languageId, store)returns the given language while it still exists. Otherwise it returns the store's default language, then the store's first language, then any language. The loyalty point handlers use it for an order withoutCustomerLanguageIdor whose language or store was deleted. Before, such an order wrote the raw resource key or threw on a null key. The registration handler uses it when the event has no language, andEndAuctionsTaskfor the admin language. When no language exists at all,EndAuctionsTasklogs a warning and skips the bid.ShoppingCartItemWarningsEventandShoppingCartWarningsEventcarry the language, so plugins that add cart warnings can write them in the same language as the core warnings.After this PR,
src/Businesshas noGetResource(name)calls. That overload, andIPermissionService.Authorize(Permission), stay for Web, modules and plugins (the remainder documented in #937).Breaking changes
IShoppingCartValidator: every method takes aLanguage language. It is the last parameter, except inGetShoppingCartItemAttributeWarnings(beforeignoreNonCombinableAttributes) andGetShoppingCartWarnings(aftercontext).ShoppingCart*ValidatorRecordrecords have a trailingLanguage Language.IShoppingCartService.AddToCart(customer, language, productId, shoppingCartType, storeId, warehouseId = null, …),UpdateShoppingCartItem(customer, language, shoppingCartItemId, warehouseId, attributes, …)andMigrateShoppingCart(fromCustomer, toCustomer, storeId, language, includeCouponCodes). The language comes right after the customer, so an old positional call does not compile instead of binding anullto the language.ShoppingCartItemWarningsEventandShoppingCartWarningsEventtake a trailingLanguage language(Languageproperty).AddRequiredProductsCommand.LanguageandReOrderCommand.Language(required).CustomerLoggedInEvent(customer, previousCustomer, storeId, language)andCustomerRegisteredEvent(customer, language). A null language means the registration had no request.IExternalAuthenticationService.Authenticate(parameters, customer, store, language, returnUrl = null). The protectedExternalAuthenticationServicehelpers take the language too.IOidcCustomerResolver.ResolveForLogin(sub, email, emailVerified, storeId, language).CustomerRegisteredEventHandler,AwardLoyaltyPointsCommandHandler,ReduceLoyaltyPointsCommandHandlerandReturnBackRedeemedLoyaltyPointsCommandHandleraddILanguageServiceandIStoreService.EndAuctionsTaskaddsILanguageService.Testing
Automated: the solution builds, and
dotnet test GrandNode.slnxpasses (22 projects). New or changed tests cover:ShoppingCartValidatorpassing the language into every record it builds;AddRequiredProductsCommandpassing its language toAddToCart;Order.CustomerLanguageId;MigrateShoppingCart;EndAuctionsTaskvalidating in the admin language.Manual: run on 2026-10-17 with Playwright against a fresh install with sample data. A second language, "Polski", has every English resource prefixed with
PL:, so each message shows which language it was written in. Loyalty points: 10 for registration, 1 point per 1 USD, awarded at Processing.["PL: Enter valid recipient name","PL: Enter valid recipient email"]PL:The server log showed no errors other than e-mail sending (no SMTP server locally).