My ASP.NET Core e-commerce project, eCommerceMVCCore, sells in English, Arabic, French, Spanish and Turkish. Early on, the language lived in a cookie. It worked for shoppers, but search engines saw a single URL with five different languages behind it, and a shared link opened in whatever language the recipient had last used.
Putting the language in the URL fixed both problems: /en/products, /ar/products, /fr/products. Here's the setup, with simplified code.
1. Add the culture to the route
Make the culture the first route segment, with a constraint so random words don't match:
app.MapControllerRoute(
name: "localized",
pattern: "{culture:regex(^(en|ar|fr|es|tr)$)}/{controller=Home}/{action=Index}/{id?}");
// "/" on its own redirects to the default language
app.MapGet("/", () => Results.Redirect("/en"));
2. Read the culture from the route
ASP.NET Core ships a RouteDataRequestCultureProvider. Put it first, so the URL wins over cookies and the browser's Accept-Language header:
var cultures = new[] { "en", "ar", "fr", "es", "tr" };
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
options.SetDefaultCulture("en")
.AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures);
options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider
{
RouteDataStringKey = "culture",
UIRouteDataStringKey = "culture"
});
});
// after UseRouting, so route values are available
app.UseRequestLocalization();
One catch: the culture provider needs route values, so UseRequestLocalization has to run after UseRouting. If the culture is always en no matter the URL, check the middleware order first.
3. Keep the culture in every generated link
Once the culture is a route value, the tag helpers carry it along automatically for links within the same request. asp-controller="Products" asp-action="Details" on an /ar/... page produces an /ar/... link. For a language switcher, set it explicitly:
<a asp-route-culture="fr" asp-controller="Home" asp-action="Index">Français</a>
4. Flip the layout for right-to-left languages
.NET already knows which cultures are right-to-left. Use it in the layout instead of hard-coding "ar":
@using System.Globalization
@{
var ui = CultureInfo.CurrentUICulture;
var dir = ui.TextInfo.IsRightToLeft ? "rtl" : "ltr";
}
<html lang="@ui.TwoLetterISOLanguageName" dir="@dir">
Then load an RTL build of your CSS framework when dir is rtl. Bootstrap has an RTL stylesheet, and the bigger job is your own CSS. Anything written as margin-left or float: right needs a second look. CSS logical properties such as margin-inline-start flip on their own and save a lot of overrides.
See it live: the multilingual ASP.NET Core store switches between
/enand/arwith a full right-to-left layout.
5. Tell search engines about the other languages
Add hreflang links to each page so Google shows the right language in each country:
@foreach (var c in new[] { "en", "ar", "fr", "es", "tr" })
{
<link rel="alternate" hreflang="@c"
href="@Url.Action(null, null, new { culture = c }, Context.Request.Scheme)" />
}
Things that tripped me up
-
Numbers and prices.
ToString("C")uses the current culture, so a price can suddenly show with a comma as the decimal separator. Decide whether currency formatting follows the language or the store's currency, and format explicitly. -
Model binding. Decimal inputs are parsed with the current culture too. A French user typing
12,50and an English user typing12.50both need to work, so test your forms in each language. -
Icons with direction. Arrows and "next" chevrons should mirror in RTL. A simple
[dir=rtl] .icon-arrow { transform: scaleX(-1); }handles most of them.
Translations from the database
I keep the UI strings in the database, not in .resx files, so translations can be edited and imported from the admin dashboard without a redeploy. That's a separate article, but the routing above works the same either way.
FAQ
How do I add multiple languages to an ASP.NET Core MVC app?
Register localization, configure RequestLocalizationOptions with your supported cultures, choose how the culture is picked (here, from the route), and serve translated strings from .resx files or a database.
Is a URL prefix, a subdomain or a cookie better for SEO?
A URL prefix or a subdomain. Search engines need a separate URL for each language; a cookie hides the other languages from crawlers. A prefix such as /fr is the simplest to run on one domain.
Does Bootstrap support right-to-left layouts?
Yes. Bootstrap 5 ships bootstrap.rtl.min.css, and Bootstrap 4 has separate RTL builds. eCommerceMVCCore uses Bootstrap with an RTL stylesheet for Arabic.
Get the source code
All of this, including five ready language packs and full RTL support, is part of eCommerceMVCCore. Get the multilingual ASP.NET Core 10 e-commerce source code.
Do you put the language in the URL, a subdomain or a cookie? I'd like to hear what worked for you.
Top comments (0)