That is the only change you need to make. The ABP application template already uses the correct middleware order — `UseAbpRequestLocalization()` comes after `UseRouting()` — so the default pipeline works as-is:
That is the only change you need to make.
```csharp
app.UseRouting();
app.UseAbpRequestLocalization(); // already in this position by default
app.UseAuthorization();
app.UseConfiguredEndpoints();
```
> This order matters because culture detection from route data is only possible after routing has assigned route values. If you have manually rearranged your middleware pipeline, make sure this order is preserved.
## What ABP Registers Automatically
## What Happens Automatically
Setting `UseRouteBasedCulture = true` triggers a cascade of automatic registrations. It is worth knowing what they are, because understanding them helps when you need to troubleshoot or extend the feature.
When you set `UseRouteBasedCulture = true`, ABP automatically:
**Route registration.** ABP inserts a `{culture}/{controller}/{action}/{id?}` conventional route *before* the default route. A matching route constraint (`^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$`) ensures only valid IETF BCP 47 language tags are accepted, so a URL like `/enterprise/products` is not mistaken for a culture-prefixed route.
- Registers ASP.NET Core's built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) to detect culture from the URL path.
- Adds a `{culture}/{controller}/{action}` conventional route for MVC controllers, with a regex constraint to prevent non-culture URL segments (like `/enterprise/products`) from matching.
- Adds `{culture}/...` route selectors to all Razor Pages at startup.
- Injects the current culture into all `Url.Page()` and `Url.Action()` calls, so generated URLs automatically include the culture prefix.
- Prepends the culture prefix to navigation menu item URLs.
**Razor Pages convention.** `AbpCultureRoutePagesConvention` adds a `{culture}/...` selector to every Razor Page route model at startup. This is what makes `/zh-Hans/Books` match the `Books/Index.cshtml` page.
**URL helper factory.** ABP replaces `IUrlHelperFactory` with `AbpCultureRouteUrlHelperFactory`. When the current request has a `{culture}` route value, every call to `Url.Page()` or `Url.Action()` automatically receives the culture as an explicit route value — no code changes needed in your views or pages.
**Menu URL provider.** `AbpCultureMenuItemUrlProvider` implements the new `IMenuItemUrlProvider` extension point. When the menu is built for a request, all local menu item URLs get the culture prefix prepended automatically. Themes and menu contributors remain completely untouched.
You do not need to configure these individually.
## URL Generation Just Works
In a Razor Page or view running under a culture-prefixed URL (say, `/zh-Hans/Books`), you do not need to pass a `culture` parameter anywhere:
```cshtml
<!-- In /zh-Hans/Books/Index.cshtml -->
@Url.Page("/Books/Detail", new { id = book.Id })
@* Generates: /zh-Hans/Books/Detail?id=42 *@
@ -59,7 +49,7 @@ In a Razor Page or view running under a culture-prefixed URL (say, `/zh-Hans/Boo
@* Generates: /zh-Hans/Home/About *@
```
`AbpCultureRouteUrlHelperFactory` injects the ambient culture value into every URL generation call. If you explicitly pass a different `culture` value, that takes precedence — so cross-language links are also straightforward:
If you explicitly pass a different `culture` value, that takes precedence — so cross-language links are also straightforward:
```cshtml
@Url.Page("/Books/Index", new { culture = "tr" })
@ -68,41 +58,23 @@ In a Razor Page or view running under a culture-prefixed URL (say, `/zh-Hans/Boo
## Language Switching
The built-in ABP language switcher (`/Abp/Languages/Switch`) already works with route-based culture. When a user switches language, the controller reads the current culture from the **request cookie** and rewrites the `{culture}` segment in the `returnUrl`.
The built-in ABP language switcher already works with route-based culture. When a user switches language, the culture segment in the URL is automatically replaced:
Reading from the request cookie (rather than `CultureInfo.CurrentCulture`) is important: the switch URL itself carries `?culture=zh-Hans` as a query parameter, which the ASP.NET Core `QueryStringRequestCultureProvider` would otherwise interpret first, overwriting the "current" culture before the controller runs.
No theme changes, no language switcher changes — the existing UI component just works.
## Blazor Server & Blazor WebAssembly (WebApp)
Blazor Server uses SignalR (WebSocket) for its interactive circuit. The HTTP middleware pipeline only runs on the **initial page load** — subsequent interactions happen over the WebSocket. ABP handles this by persisting the detected URL culture to a cookie on the first request, so the entire Blazor circuit uses the correct language.
Blazor WebAssembly (WebApp) is similar. The server renders the first page via SSR and detects the culture from the URL. After WASM downloads, subsequent renders run in the browser. The WASM app reads the culture from the server's `/api/abp/application-configuration` response, so the culture stays consistent.
### What works automatically
| Feature | Blazor Server | Blazor WebApp (WASM) |
|---|---|---|
| **Culture detection** | `RouteDataRequestCultureProvider` reads `{culture}` on the initial HTTP request (SSR). | Same — SSR on first load. |
| **Cookie persistence** | Middleware saves the culture to `.AspNetCore.Culture` cookie, which persists across the WebSocket connection. | Cookie is set during SSR. WASM reads the `UseRouteBasedCulture` flag from `/api/abp/application-configuration`. |
| **Menu URLs** | `AbpCultureMenuItemUrlProvider` prepends the culture prefix. Falls back to `CultureInfo.CurrentCulture` in interactive circuits where `HttpContext` is null. | `AbpWasmCultureMenuItemUrlProvider` reads the flag and language list from the cached application configuration. |
| **Language switching** | `LanguageSwitch` navigates to `/Abp/Languages/Switch` with `forceLoad: true`, triggering a full HTTP reload. | `LanguageSwitch` replaces the culture segment in the URL client-side and navigates with `forceLoad: true`. |
### Important: Blazor component route limitation
## Blazor Support
In MVC / Razor Pages, ABP uses `AbpCultureRoutePagesConvention` (an `IPageRouteModelConvention`) to **automatically** add `{culture}/...` route selectors to every page at startup. No code changes needed.
Blazor Server and Blazor WebAssembly (WebApp) both support URL-based localization. Culture detection and cookie persistence work automatically on the initial page load (SSR). Menu URLs and language switching also work automatically.
**Blazor components do not have this capability.** ASP.NET Core does not provide an `IPageRouteModelConvention` equivalent for Blazor components. Blazor routes are compiled from `@page` directives into `[RouteAttribute]` at build time, with no runtime extension point. This is an [ASP.NET Core platform limitation](https://github.com/dotnet/aspnetcore/issues/57167), not an ABP limitation.
### Manual step: Blazor component routes
You must **manually** add the `{culture}` route to each of your own Blazor pages:
The only manual step for Blazor is adding `@page "/{culture}/..."` routes to your own pages. ASP.NET Core does not support automatically adding route selectors to Blazor components (unlike Razor Pages), so you must add them explicitly:
```razor
@page "/"
@ -126,70 +98,40 @@ You must **manually** add the `{culture}` route to each of your own Blazor pages
> **ABP's built-in module pages** (Identity, Tenant Management, Settings, Account, etc.) already ship with `@page "/{culture}/..."` route variants. You only need to add these routes to your own application pages.
### Language switching uses forceLoad
### Blazor WebApp (WASM) configuration
Language switching in Blazor triggers a **full page reload** rather than a SPA-style client navigation. This is by design — switching languages requires re-rendering all localized text (menus, labels, content) with the new culture.
**Blazor Server** navigates to `/Abp/Languages/Switch` on the server, which rewrites the culture segment and redirects back:
// WASM client project — no UseRouteBasedCulture configuration needed.
// The WASM client reads the flag from the server via /api/abp/application-configuration.
```
## Multi-Tenancy
URL-based localization is fully compatible with ABP's multi-tenant routing. The culture route is handled as a separate routing layer from the tenant. Language switching explicitly supports tenant-prefixed URLs, so `/tenant-a/zh-Hans/About → /tenant-a/en/About` works without any additional configuration.
> For details on combining tenant routing with culture routing, see the [Multi-Tenancy](https://abp.io/docs/latest/framework/architecture/multi-tenancy) documentation.
URL-based localization is fully compatible with ABP's multi-tenant routing. Language switching supports tenant-prefixed URLs, so `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About` without any additional configuration.
## UI Framework Support Overview
| UI Framework | Route Registration | URL Generation | Menu URLs | Language Switch | Manual Work |
To add SEO-friendly localized URL paths to your ABP application:
1. Set `options.UseRouteBasedCulture = true` in your module.
2. Ensure `UseAbpRequestLocalization()` comes after `UseRouting()` in the pipeline.
3. For **Blazor** projects, add `@page "/{culture}/..."` routes to your own pages.
2. For **Blazor** projects, add `@page "/{culture}/..."` routes to your own pages.
ABP automatically registers the culture route, adds `{culture}/...` selectors to all Razor Pages, rewrites URLs generated by `Url.Page()` and `Url.Action()`, updates navigation menus, and handles language switching — all without any changes to themes, menu contributors, or views.
Everything else — route registration, URL generation, menu links, and language switching — is handled automatically.
A runnable sample demonstrating this feature is available at [abp-samples/UrlBasedLocalization](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization). It includes MVC, Blazor Server, and Blazor WebApp projects with English, Turkish, French, and Simplified Chinese.
A runnable sample is available at [abp-samples/UrlBasedLocalization](https://github.com/abpframework/abp-samples/tree/master/UrlBasedLocalization). It includes MVC, Blazor Server, and Blazor WebApp projects with English, Turkish, French, and Simplified Chinese.
## References
@ -197,4 +139,3 @@ A runnable sample demonstrating this feature is available at [abp-samples/UrlBas
That's all you need. The framework automatically handles the rest.
> You also need to ensure that `UseAbpRequestLocalization()` is called **after**`UseRouting()` in your middleware pipeline. See the [Middleware Order](#middleware-order) section below.
## What Happens Automatically
When you set `UseRouteBasedCulture` to `true`, ABP automatically registers the following:
* **`RouteDataRequestCultureProvider`** — Reads `{culture}` from route data (highest priority provider).
* **`{culture}/{controller}/{action}` route** — A conventional route for MVC controllers.
* **`RouteDataRequestCultureProvider`** — A built-in ASP.NET Core provider that reads `{culture}` from route data. ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`.
* **`{culture:regex(...)}/{controller}/{action}` route** — A conventional route for MVC controllers. The `{culture}` parameter includes a regex constraint matching IETF BCP 47 language tags, so URLs like `/enterprise/products` are not mistaken for culture-prefixed routes.
* **`AbpCultureRoutePagesConvention`** — An `IPageRouteModelConvention` that adds `{culture}/...` route selectors to all Razor Pages.
* **`AbpCultureRouteUrlHelperFactory`** — Replaces the default `IUrlHelperFactory` to auto-inject culture into `Url.Page()` and `Url.Action()` calls.
* **`AbpCultureMenuItemUrlProvider`** — Prepends the culture prefix to navigation menu item URLs (MVC / Blazor Server).
@ -39,20 +37,6 @@ When you set `UseRouteBasedCulture` to `true`, ABP automatically registers the f
You do not need to configure these individually.
## Middleware Order
URL-based localization requires `UseAbpRequestLocalization()` to be called **after**`UseRouting()`:
````csharp
app.MapAbpStaticAssets();
app.UseRouting();
app.UseAbpRequestLocalization(); // Must be after UseRouting()
app.UseAuthorization();
app.UseConfiguredEndpoints();
````
> If you do not enable `UseRouteBasedCulture`, the middleware order does not matter and your existing application continues to work as before.
## URL Generation
When a request has a `{culture}` route value, all URL generation methods automatically include the culture prefix:
@ -63,8 +47,6 @@ When a request has a `{culture}` route value, all URL generation methods automat
This works because `AbpCultureRouteUrlHelperFactory` replaces the default `IUrlHelperFactory` and injects the current `{culture}` route value into all URL generation calls.
Menu items registered via `IMenuContributor` also automatically get the culture prefix. No changes are needed in your menu contributors or theme.
## Language Switching
@ -82,38 +64,13 @@ No changes are needed in any theme or language switcher component.
## MVC / Razor Pages
MVC and Razor Pages have the most complete support. Everything works automatically when `UseRouteBasedCulture = true`:
| Feature | How it works |
|---|---|
| **Route registration** | `AbpCultureRoutePagesConvention` adds `{culture}/...` route selectors to every Razor Page. MVC controllers get a `{culture}/{controller}/{action}` conventional route. |
| **URL generation** | `AbpCultureRouteUrlHelperFactory` wraps `IUrlHelperFactory` to auto-inject the `{culture}` route value into `Url.Page()`, `Url.Action()`, and tag helpers. |
| **Menu URLs** | `AbpCultureMenuItemUrlProvider` prepends the culture prefix to all local menu item URLs. |
| **Language switching** | The `/Abp/Languages/Switch` action replaces the culture segment in the return URL and sets the cookie. |
**No code changes are needed in your pages or controllers.** The framework handles everything.
### Example middleware pipeline
````csharp
app.UseRouting();
app.UseAbpRequestLocalization();
app.UseAuthorization();
app.UseConfiguredEndpoints();
````
MVC and Razor Pages have the most complete support. Everything works automatically when `UseRouteBasedCulture = true` — route registration, URL generation, menu links, and language switching. **No code changes are needed in your pages or controllers.**
## Blazor Server
Blazor Server uses SignalR (WebSocket) for the interactive circuit. The HTTP middleware pipeline only runs on the **initial page load** — subsequent interactions happen over the WebSocket connection. ABP handles this by persisting the detected URL culture to a **Cookie** on the first request, so the entire Blazor circuit uses the correct language.
### What works automatically
| Feature | How it works |
|---|---|
| **Culture detection** | `RouteDataRequestCultureProvider` reads `{culture}` from the URL on the initial HTTP request (SSR). |
| **Cookie persistence** | The middleware automatically saves the detected culture to `.AspNetCore.Culture` cookie, which persists across the WebSocket connection. |
| **Menu URLs** | `AbpCultureMenuItemUrlProvider` prepends the culture prefix. In the interactive circuit (where `HttpContext` is null — no active HTTP request), it falls back to `CultureInfo.CurrentCulture`. |
| **Language switching** | The built-in `LanguageSwitch` component navigates to `/Abp/Languages/Switch` with `forceLoad: true`, triggering a full HTTP reload. The culture segment in the return URL is automatically replaced. |
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. No additional configuration is needed beyond the `UseRouteBasedCulture` option.
### What requires manual changes
@ -141,74 +98,16 @@ Blazor Server uses SignalR (WebSocket) for the interactive circuit. The HTTP mid
> This applies to your own application pages. ABP built-in module pages (Identity, Tenant Management, Settings, Account, etc.) already include `@page "/{culture}/..."` routes out of the box — you do not need to add them manually.
Blazor WebAssembly (WASM) runs in the browser. On the **first page load**, the server renders the page via SSR, and the culture is detected from the URL. After WASM downloads, subsequent renders run in the browser. The WASM app fetches `/api/abp/application-configuration` from the server to get the current culture, so the culture stays consistent.
### What works automatically
| Feature | How it works |
|---|---|
| **SSR culture detection** | Same as Blazor Server — `RouteDataRequestCultureProvider` reads `{culture}` on the initial HTTP request. |
| **Cookie persistence** | The cookie is set during SSR, ensuring the WASM client inherits the correct culture. |
| **Menu URLs** | `AbpWasmCultureMenuItemUrlProvider` prepends the culture prefix to menu items. The `UseRouteBasedCulture` flag is read from `/api/abp/application-configuration`. |
| **Language switching** | The built-in `LanguageSwitch` component replaces the culture segment in the current URL and navigates with `forceLoad: true`, triggering a full page reload with the new culture. |
| **Login link** | The `LoginDisplay` component automatically prepends the culture prefix to the login URL when route-based culture is active. |
Culture detection, cookie persistence, menu URLs, and language switching all work automatically. The WASM client reads the `UseRouteBasedCulture` flag from the server via `/api/abp/application-configuration`, so no client-side configuration is needed.
### What requires manual changes
Same as Blazor Server — you must manually add `@page "/{culture}/..."` routes to your Blazor pages.
// WASM client project — no special UseRouteBasedCulture configuration needed.
// The WASM client reads the flag from the server via /api/abp/application-configuration.
````
### Example middleware pipeline
````csharp
app.UseRouting();
app.UseAbpRequestLocalization();
app.UseAntiforgery();
app.UseConfiguredEndpoints(builder =>
{
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(clientAssembly);
});
````
## Multi-Tenancy Compatibility
URL-based localization is fully compatible with [multi-tenancy URL routing](../architecture/multi-tenancy/index.md). The culture route is registered as a conventional route `{culture}/{controller}/{action}`. If your application uses tenant routing (e.g., `/{tenant}/...`), the tenant middleware strips the tenant segment before routing, and the culture segment is handled separately.
@ -221,17 +120,11 @@ Routes like `/api/products` have no `{culture}` segment, so `RouteDataRequestCul
## Culture Detection Priority
All request culture providers work together in priority order. When `UseRouteBasedCulture` is enabled, the route-based provider is added as the highest priority:
ASP.NET Core has a built-in [`RouteDataRequestCultureProvider`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.routing.routedatarequestcultureprovider) (in `Microsoft.AspNetCore.Localization.Routing`) that reads culture from route data, but it is not included in the default provider list. When `UseRouteBasedCulture` is enabled, ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`. The resulting provider order is:
1. `RouteDataRequestCultureProvider` (URL path — highest priority when enabled)
2. `QueryStringRequestCultureProvider`
3. `CookieRequestCultureProvider`
4. `AcceptLanguageHeaderRequestCultureProvider`
1. `QueryStringRequestCultureProvider` (ASP.NET Core default — useful for debugging and testing)
2. `RouteDataRequestCultureProvider` (URL path — inserted by ABP when enabled)
If a URL contains an invalid culture code (e.g. `/xyz1234/page`), `RequestLocalizationMiddleware` ignores it and falls through to the next provider. No error is thrown.
## Route Registration
ABP automatically registers both `{culture}/{controller}/{action}` and `{controller}/{action}` routes. The non-prefixed route handles direct navigation to `/` and URLs without a culture segment.
> ABP built-in module pages (Identity, Tenant Management, Settings, Account, etc.) already include `@page "/{culture}/..."` route variants out of the box. You only need to add these routes to your own application pages.