Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Libraries/Nop.Core/Domain/Common/SitemapXmlSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ public SitemapXmlSettings()
/// Gets or sets the wait time (in seconds) before the operation can be started again
/// </summary>
public int SitemapBuildOperationDelay { get; set; }

/// <summary>
/// Disallow languages
/// </summary>
public List<int> DisallowLanguages { get; set; } = new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To configure it, you will have to add UI similar to RobotsTxtSettings.DisallowLanguages

Copy link
Contributor Author

@bambuca bambuca Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now there is no UI for administrating the SitemapXmlSettings so I will add new section for all settings, not just for this new property if you are ok with that?

}
22 changes: 15 additions & 7 deletions src/Presentation/Nop.Web/Factories/SitemapModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,18 +878,26 @@ var name when name.Equals(nameof(ProductTag), StringComparison.InvariantCultureI
_ => GetUrlHelper().RouteUrl(routeName, values, protocol)
};

//url for current language
var url = await routeUrlAsync(routeName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code does not need to be removed. By default, the main address is the address without localization

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! However, I'd like to clarify why we replaced this part:

var url = await routeUrlAsync(routeName,
    getRouteParamsAwait != null ? await getRouteParamsAwait(null) : null,
    await GetHttpProtocolAsync());

with this one:

var languages = _localizationSettings.SeoFriendlyUrlsForLanguagesEnabled
    ? (await _languageService.GetAllLanguagesAsync(storeId: store.Id))
        .Where(lang => !_sitemapXmlSettings.DisallowLanguages.Contains(lang.Id))
        .ToList()
    : null;

var workingLanguage = await _workContext.GetWorkingLanguageAsync();
var language = languages?.FirstOrDefault(lang => lang.Id == store.DefaultLanguageId)
    ?? languages?.FirstOrDefault()
    ?? workingLanguage;

var url = await routeUrlAsync(
    routeName,
    getRouteParamsAwait != null ? await getRouteParamsAwait(language.Id) : null,
    await GetHttpProtocolAsync());

if (language.Id != workingLanguage.Id)
    url = GetLocalizedUrl(url, language);

Why this change?

Even though the original version uses getRouteParamsAwait(null), that null does not eliminate language context. In fact:

  • For some entities (like News), LanguageId is passed explicitly (e.g., news.LanguageId).
  • For others, like in GetSeoRouteParamsAwait(...), the LanguageId is not provided, and the system falls back to the current language from IWorkContext, resolved in _urlRecordService.GetSeNameAsync(...).

This makes the result dependent on the active user/session, which is undesirable for sitemap generation.

In addition to that, the default IOutboundParameterTransformer implementation in LanguageParameterTransformer.cs also:

  • First checks for the language in RouteValues
  • Then falls back to IWorkContext

This behavior is not fully deterministic and leads to inconsistencies in the resulting URLs.

That’s why we explicitly select a language for sitemap URL generation:

  • Prefer the default store language (if allowed)
  • Fallback to the first allowed language
  • Fallback to the current one (last resort)

And finally, we use this condition:

if (language.Id != workingLanguage.Id)
    url = GetLocalizedUrl(url, language);

to ensure the generated URL reflects the explicitly chosen language, overriding whatever may have been implicitly applied via IOutboundParameterTransformer.

This guarantees that all sitemap entries are generated consistently and intentionally, regardless of the current user context or route state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I understand your idea and the problem you are solving, but it is not related to the ticket itself. Let's create a separate ticket and a separate pull request for this task. I will review everything again. As for the task #7777 itself, we decided not to implement it in the core for now

getRouteParamsAwait != null ? await getRouteParamsAwait(null) : null,
await GetHttpProtocolAsync());

var store = await _storeContext.GetCurrentStoreAsync();

var updatedOn = dateTimeUpdatedOn ?? DateTime.UtcNow;
var languages = _localizationSettings.SeoFriendlyUrlsForLanguagesEnabled
? await _languageService.GetAllLanguagesAsync(storeId: store.Id)
? (await _languageService.GetAllLanguagesAsync(storeId: store.Id))
.Where(lang => !_sitemapXmlSettings.DisallowLanguages.Contains(lang.Id)).ToList()
: null;

// select store default language if allowed, fallback to first allowed if needed
var workingLanguage = await _workContext.GetWorkingLanguageAsync();
var language = languages?.FirstOrDefault(lang => lang.Id == store.DefaultLanguageId) ?? languages?.FirstOrDefault() ?? workingLanguage;

//url for current language
var url = await routeUrlAsync(routeName,
getRouteParamsAwait != null ? await getRouteParamsAwait(language.Id) : null,
await GetHttpProtocolAsync());

if (language.Id != workingLanguage.Id)
url = GetLocalizedUrl(url, language);

if (languages == null || languages.Count == 1)
return new SitemapUrlModel(url, new List<string>(), updateFreq, updatedOn);

Expand All @@ -901,7 +909,7 @@ var name when name.Equals(nameof(ProductTag), StringComparison.InvariantCultureI
getRouteParamsAwait != null ? await getRouteParamsAwait(lang.Id) : null,
await GetHttpProtocolAsync());

return GetLocalizedUrl(currentUrl, lang);
return lang.Id != workingLanguage.Id ? GetLocalizedUrl(currentUrl, lang) : currentUrl;
})
.Where(value => !string.IsNullOrEmpty(value))
.ToListAsync();
Expand Down