|
| 1 | +````json |
| 2 | +//[doc-seo] |
| 3 | +{ |
| 4 | + "Description": "Learn how to use ABP's URL-based localization to embed culture in the URL path, enabling SEO-friendly and shareable localized URLs." |
| 5 | +} |
| 6 | +```` |
| 7 | + |
| 8 | +# URL-Based Localization |
| 9 | + |
| 10 | +ABP supports embedding the current culture directly in the URL path, for example `/tr/products` or `/en/about`. This approach is widely used by documentation sites, e-commerce platforms, and any site that needs SEO-friendly, shareable localized URLs. |
| 11 | + |
| 12 | +By default, ABP detects language from QueryString (`?culture=tr`), Cookie, and `Accept-Language` header. URL path detection is **opt-in** and fully backward-compatible. |
| 13 | + |
| 14 | +## Enabling URL-Based Localization |
| 15 | + |
| 16 | +Configure the `AbpRequestLocalizationOptions` in your [module class](../architecture/modularity/basics.md): |
| 17 | + |
| 18 | +````csharp |
| 19 | +Configure<AbpRequestLocalizationOptions>(options => |
| 20 | +{ |
| 21 | + options.UseRouteBasedCulture = true; |
| 22 | +}); |
| 23 | +```` |
| 24 | + |
| 25 | +That's all you need. The framework automatically handles the rest. |
| 26 | + |
| 27 | +## What Happens Automatically |
| 28 | + |
| 29 | +When you set `UseRouteBasedCulture` to `true`, ABP automatically registers the following: |
| 30 | + |
| 31 | +* **`RouteDataRequestCultureProvider`** — A built-in ASP.NET Core provider that reads `{culture}` from route data. ABP inserts it after `QueryStringRequestCultureProvider` and before `CookieRequestCultureProvider`. |
| 32 | +* **`{culture}/{controller}/{action}` route** — A conventional route for MVC controllers. The `{culture}` parameter uses a custom route constraint (`AbpCultureRouteConstraint`) that only matches culture values configured in `AbpLocalizationOptions.Languages`, so URLs like `/enterprise/products` are not mistaken for culture-prefixed routes. |
| 33 | +* **`AbpCultureRoutePagesConvention`** — An `IPageRouteModelConvention` that adds `{culture}/...` route selectors to all Razor Pages. |
| 34 | +* **`AbpCultureRouteUrlHelperFactory`** — Replaces the default `IUrlHelperFactory` to auto-inject culture into `Url.Page()` and `Url.Action()` calls. |
| 35 | +* **`AbpCultureMenuItemUrlProvider`** — Prepends the culture prefix to navigation menu item URLs (MVC / Blazor Server). |
| 36 | +* **`AbpWasmCultureMenuItemUrlProvider`** — Prepends the culture prefix to menu item URLs in Blazor WebAssembly (reads the `UseRouteBasedCulture` flag from `/api/abp/application-configuration`). |
| 37 | + |
| 38 | +You do not need to configure these individually. |
| 39 | + |
| 40 | +## URL Generation |
| 41 | + |
| 42 | +When a request has a `{culture}` route value, all URL generation methods automatically include the culture prefix: |
| 43 | + |
| 44 | +````csharp |
| 45 | +// In a Razor Page — culture is auto-injected, no manual parameter needed |
| 46 | +@Url.Page("/About") // Generates: /zh-Hans/About |
| 47 | +@Url.Action("About", "Home") // Generates: /zh-Hans/Home/About |
| 48 | +```` |
| 49 | + |
| 50 | +Menu items registered via `IMenuContributor` also automatically get the culture prefix. No changes are needed in your menu contributors or theme. |
| 51 | + |
| 52 | +## Language Switching |
| 53 | + |
| 54 | +ABP's built-in language switcher (the `/Abp/Languages/Switch` action) automatically replaces the culture segment in the `returnUrl`. The controller reads the culture from the request cookie to identify the current page culture and replaces it with the new one: |
| 55 | + |
| 56 | +| Before switching | After switching to English | |
| 57 | +|---|---| |
| 58 | +| `/tr/products` | `/en/products` | |
| 59 | +| `/tenant-a/zh-Hans/about` | `/tenant-a/en/about` | |
| 60 | +| `/home?culture=tr&ui-culture=tr` | `/home?culture=en&ui-culture=en` | |
| 61 | +| `/about` (no prefix) | `/about` (unchanged) | |
| 62 | + |
| 63 | +No changes are needed in any theme or language switcher component. |
| 64 | + |
| 65 | +## MVC / Razor Pages |
| 66 | + |
| 67 | +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.** |
| 68 | + |
| 69 | +## Blazor Server |
| 70 | + |
| 71 | +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. |
| 72 | + |
| 73 | +Culture detection, cookie persistence, menu URLs, and language switching all work automatically. No additional configuration is needed beyond the `UseRouteBasedCulture` option. |
| 74 | + |
| 75 | +### What requires manual changes |
| 76 | + |
| 77 | +**Blazor component routes**: ASP.NET Core does not provide an `IPageRouteModelConvention` equivalent for Blazor components. You must manually add the `{culture}` route to each page: |
| 78 | + |
| 79 | +````razor |
| 80 | +@page "/" |
| 81 | +@page "/{culture}" |
| 82 | +
|
| 83 | +@code { |
| 84 | + [Parameter] |
| 85 | + public string? Culture { get; set; } |
| 86 | +} |
| 87 | +```` |
| 88 | + |
| 89 | +````razor |
| 90 | +@page "/About" |
| 91 | +@page "/{culture}/About" |
| 92 | +
|
| 93 | +@code { |
| 94 | + [Parameter] |
| 95 | + public string? Culture { get; set; } |
| 96 | +} |
| 97 | +```` |
| 98 | + |
| 99 | +> 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. |
| 100 | +
|
| 101 | +## Blazor WebAssembly (WebApp) |
| 102 | + |
| 103 | +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. |
| 104 | + |
| 105 | +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. |
| 106 | + |
| 107 | +### What requires manual changes |
| 108 | + |
| 109 | +Same as Blazor Server — you must manually add `@page "/{culture}/..."` routes to your Blazor pages. |
| 110 | + |
| 111 | +## Angular |
| 112 | + |
| 113 | +The [ABP Angular UI](../ui/angular/quick-start.md) runs in the browser. The server still applies `UseRouteBasedCulture`; the client reads **`localization.useRouteBasedCulture`** from `/api/abp/application-configuration` (same payload as other UI types). There is no separate Angular setting. |
| 114 | + |
| 115 | +### Routing |
| 116 | + |
| 117 | +Angular does not add a culture segment to your route config automatically. Use **`withOptionalRouteCulturePrefix`** from **`@abp/ng.core`** so one route tree matches both **`/identity/users`** and **`/en/identity/users`** (the first path segment is matched only when it looks like a culture code, e.g. `en`, `tr`, `zh-Hans`). |
| 118 | + |
| 119 | +````typescript |
| 120 | +import { Routes } from '@angular/router'; |
| 121 | +import { withOptionalRouteCulturePrefix } from '@abp/ng.core'; |
| 122 | + |
| 123 | +const appRoutesCore: Routes = [ |
| 124 | + // ... your routes (path: '', 'account', 'identity', lazy children, etc.) |
| 125 | +]; |
| 126 | + |
| 127 | +export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore); |
| 128 | +```` |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | +### URL → session language |
| 133 | + |
| 134 | +When **`useRouteBasedCulture`** is **true**, **`RouteBasedCultureService`** (from `@abp/ng.core`) keeps the session language aligned with the first URL segment after navigation. This runs during application bootstrap and on each **`NavigationEnd`**. |
| 135 | + |
| 136 | +### Menu links, breadcrumbs, and `routerLink` |
| 137 | + |
| 138 | +Menu paths from **`RoutesService`** are usually **without** a culture prefix (`/identity/users`). Use the **`abpRouteCultureUrl`** pipe on **`routerLink`** (or **`RouteBasedCultureUrlService.prefixPathWithCulture`**) so links navigate to **`/en/identity/users`** when route-based culture is enabled. The **Basic** theme navigation and **Theme Shared** breadcrumb links follow this pattern. |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | +### Language switcher (toolbar) |
| 143 | + |
| 144 | +If the user selects a language in the UI, call **`RouteBasedCultureUrlService.applyLanguageSelection(cultureName)`** (or **`navigateToUrlWithCulture`**) instead of only updating the session language. That rewrites the current URL’s culture segment (or prepends it) so the address bar and session stay consistent; **`RouteBasedCultureService`** then picks up the culture from the URL after navigation. |
| 145 | + |
| 146 | +### Active menu, breadcrumbs, and route matching |
| 147 | + |
| 148 | +The browser URL may be **`/en/identity/users`** while menu items and **`RoutesService`** paths stay **`/identity/users`**. For comparisons (active state, **`findRoute`**, permission guard, dynamic layout), normalize the current URL with **`RouteBasedCultureUrlService.normalizeForMenuMatch`** (or **`stripCulturePrefixIfEnabled`**) or use **`getRoutePathForMatching`** where **`getRoutePath`** was used. |
| 149 | + |
| 150 | +### Configuration refresh |
| 151 | + |
| 152 | +**`RouteBasedCultureUrlService`** refreshes its cached **`useRouteBasedCulture`** and **languages** when application configuration is updated (for example after **`refreshAppState`**), so hot paths do not query configuration on every change detection cycle. |
| 153 | + |
| 154 | +## Multi-Tenancy Compatibility |
| 155 | + |
| 156 | +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. |
| 157 | + |
| 158 | +Language switching also supports tenant-prefixed URLs. For example, `/tenant-a/zh-Hans/About` correctly switches to `/tenant-a/en/About`. |
| 159 | + |
| 160 | +## API Routes |
| 161 | + |
| 162 | +Routes like `/api/products` have no `{culture}` segment, so `RouteDataRequestCultureProvider` returns `null` and falls through to the next provider (Cookie → `Accept-Language` → default). API routes are completely unaffected. |
| 163 | + |
| 164 | +## Culture Detection Priority |
| 165 | + |
| 166 | +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: |
| 167 | + |
| 168 | +1. `QueryStringRequestCultureProvider` (ASP.NET Core default — useful for debugging and testing) |
| 169 | +2. `RouteDataRequestCultureProvider` (URL path — inserted by ABP when enabled) |
| 170 | +3. `CookieRequestCultureProvider` (ASP.NET Core default) |
| 171 | +4. `AcceptLanguageHeaderRequestCultureProvider` (ASP.NET Core default) |
| 172 | + |
| 173 | +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. |
0 commit comments