Skip to content

Commit 3f7b6d5

Browse files
committed
implement folder segment localization
1 parent fcb4678 commit 3f7b6d5

8 files changed

Lines changed: 193 additions & 68 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Routing;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Options;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
9+
namespace cloudscribe.Web.Localization
10+
{
11+
public class CultureSegmentRouteConstraint : IRouteConstraint
12+
{
13+
public bool Match(
14+
HttpContext httpContext,
15+
IRouter route,
16+
string routeKey,
17+
RouteValueDictionary values,
18+
RouteDirection routeDirection)
19+
{
20+
if (httpContext == null) { return false; }
21+
22+
23+
24+
string requestFolder = GetStartingSegment(httpContext.Request.Path);
25+
if (!string.IsNullOrWhiteSpace(requestFolder))
26+
{
27+
var cultureSettingsAccessor = httpContext.RequestServices.GetService<IOptions<RequestLocalizationOptions>>();
28+
var cultureSettings = cultureSettingsAccessor.Value;
29+
var found = cultureSettings.SupportedUICultures.Where(x => x.Name == requestFolder || x.TwoLetterISOLanguageName == requestFolder).Any();
30+
var isDefaultCulture = cultureSettings.DefaultRequestCulture.UICulture.Name == requestFolder || cultureSettings.DefaultRequestCulture.UICulture.TwoLetterISOLanguageName == requestFolder;
31+
//don't match default culture because we don't want the culture segment in the url for the default culture
32+
if (found && !isDefaultCulture)
33+
{
34+
return true;
35+
}
36+
}
37+
38+
39+
return false;
40+
}
41+
42+
private string GetStartingSegment(string requestPath)
43+
{
44+
if (string.IsNullOrEmpty(requestPath)) return requestPath;
45+
if (!requestPath.Contains("/")) return requestPath;
46+
47+
var segments = SplitOnCharAndTrim(requestPath, '/');
48+
return segments.FirstOrDefault();
49+
}
50+
51+
private List<string> SplitOnCharAndTrim(string s, char c)
52+
{
53+
List<string> list = new List<string>();
54+
if (string.IsNullOrWhiteSpace(s)) { return list; }
55+
56+
string[] a = s.Split(c);
57+
foreach (string item in a)
58+
{
59+
if (!string.IsNullOrWhiteSpace(item)) { list.Add(item.Trim()); }
60+
}
61+
62+
63+
return list;
64+
}
65+
}
66+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Localization;
3+
using System.Collections.Generic;
4+
using System.Globalization;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace cloudscribe.Web.Localization
9+
{
10+
public class FirstUrlSegmentRequestCultureProvider : RequestCultureProvider
11+
{
12+
public FirstUrlSegmentRequestCultureProvider(
13+
IList<CultureInfo> supportedUICultures,
14+
IList<CultureInfo> supportedCultures = null
15+
)
16+
{
17+
_supportedUICultures = supportedUICultures;
18+
_supportedCultures = supportedCultures;
19+
}
20+
21+
private readonly IList<CultureInfo> _supportedUICultures;
22+
private readonly IList<CultureInfo> _supportedCultures;
23+
24+
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
25+
{
26+
var pathStartingSegment = GetStartingSegment(httpContext.Request.Path);
27+
28+
if (!string.IsNullOrWhiteSpace(pathStartingSegment))
29+
{
30+
var matchingUICulture = _supportedUICultures.Where(x => x.Name == pathStartingSegment || x.TwoLetterISOLanguageName == pathStartingSegment).FirstOrDefault();
31+
CultureInfo mainCulture = null;
32+
if (_supportedCultures != null)
33+
{
34+
mainCulture = _supportedCultures.Where(x => x.Name == pathStartingSegment || x.TwoLetterISOLanguageName == pathStartingSegment).FirstOrDefault();
35+
}
36+
if (matchingUICulture != null)
37+
{
38+
if (mainCulture != null)
39+
{
40+
return Task.FromResult(new ProviderCultureResult(mainCulture.Name, matchingUICulture.Name));
41+
}
42+
return Task.FromResult(new ProviderCultureResult(matchingUICulture.Name, matchingUICulture.Name));
43+
}
44+
}
45+
46+
//nothing matched
47+
return NullProviderCultureResult;
48+
49+
}
50+
51+
private string GetStartingSegment(string requestPath)
52+
{
53+
if (string.IsNullOrEmpty(requestPath)) return requestPath;
54+
if (!requestPath.Contains("/")) return requestPath;
55+
56+
var segments = SplitOnCharAndTrim(requestPath, '/');
57+
return segments.FirstOrDefault();
58+
}
59+
60+
private List<string> SplitOnCharAndTrim(string s, char c)
61+
{
62+
List<string> list = new List<string>();
63+
if (string.IsNullOrWhiteSpace(s)) { return list; }
64+
65+
string[] a = s.Split(c);
66+
foreach (string item in a)
67+
{
68+
if (!string.IsNullOrWhiteSpace(item)) { list.Add(item.Trim()); }
69+
}
70+
71+
72+
return list;
73+
}
74+
}
75+
}

src/cloudscribe.Web.Localization/Properties/AssemblyInfo.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/cloudscribe.Web.Localization/cloudscribe.Web.Localization.csproj

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22

33
<PropertyGroup>
44
<Description>more flexible localization for ASP.NET Core</Description>
5-
<VersionPrefix>2.0.0</VersionPrefix>
5+
<VersionPrefix>2.0.1</VersionPrefix>
66
<Authors>Joe Audette</Authors>
77
<TargetFrameworks>netstandard2.0</TargetFrameworks>
88
<AssemblyName>cloudscribe.Web.Localization</AssemblyName>
99
<PackageId>cloudscribe.Web.Localization</PackageId>
1010
<PackageTags>cloudscribe;localization;resx</PackageTags>
1111
<PackageIconUrl>https://raw.githubusercontent.com/joeaudette/cloudscribe/master/cloudscribe-icon-32.png</PackageIconUrl>
1212
<PackageProjectUrl>https://github.com/joeaudette/cloudscribe.Web.Localization</PackageProjectUrl>
13-
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
14-
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
15-
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
16-
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
13+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
14+
<RepositoryUrl>https://github.com/cloudscribe/cloudscribe.Web.Localization.git</RepositoryUrl>
15+
<RepositoryType>git</RepositoryType>
16+
1717
</PropertyGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.*" />
21-
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.0.*" />
22-
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.*" />
20+
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.1.1" />
21+
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.1.1" />
22+
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.1.1" />
23+
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.1.1" />
24+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
2325
</ItemGroup>
2426

27+
2528

2629
</Project>

src/localization.WebApp/Startup.cs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.AspNetCore.Localization;
1515
using Microsoft.Extensions.Options;
1616
using Microsoft.Extensions.Localization;
17+
using cloudscribe.Web.Localization;
1718

1819
//https://github.com/aspnet/Localization/issues/157
1920

@@ -47,19 +48,22 @@ public void ConfigureServices(IServiceCollection services)
4748
.AddDataAnnotationsLocalization()
4849
;
4950

50-
51-
// this seems in conflict with middleware config below where we new up the options
52-
// but without this the language dropdown in layout only shows english
53-
services.Configure<RequestLocalizationOptions>(options =>
54-
{
55-
var supportedCultures = new[]
51+
var supportedCultures = new[]
5652
{
5753
new CultureInfo("en-US"),
58-
new CultureInfo("en"),
54+
//new CultureInfo("en"),
5955
new CultureInfo("fr-FR"),
60-
new CultureInfo("fr"),
56+
// new CultureInfo("fr"),
6157
};
6258

59+
var routeSegmentLocalizationProvider = new FirstUrlSegmentRequestCultureProvider(supportedCultures.ToList());
60+
61+
// this seems in conflict with middleware config below where we new up the options
62+
// but without this the language dropdown in layout only shows english
63+
services.Configure<RequestLocalizationOptions>(options =>
64+
{
65+
66+
6367
// State what the default culture for your application is. This will be used if no specific culture
6468
// can be determined for a given request.
6569
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
@@ -83,18 +87,25 @@ public void ConfigureServices(IServiceCollection services)
8387
// // My custom request culture logic
8488
// return new ProviderCultureResult("en");
8589
//}));
90+
options.RequestCultureProviders.Insert(0, routeSegmentLocalizationProvider);
91+
8692
});
8793
}
8894

8995
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
90-
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
96+
public void Configure(
97+
IApplicationBuilder app,
98+
IHostingEnvironment env,
99+
ILoggerFactory loggerFactory,
100+
IOptions<RequestLocalizationOptions> locOptions
101+
)
91102
{
92103

93104
if (env.IsDevelopment())
94105
{
95106
app.UseDeveloperExceptionPage();
96107
app.UseDatabaseErrorPage();
97-
app.UseBrowserLink();
108+
98109
}
99110
else
100111
{
@@ -103,13 +114,22 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
103114

104115
app.UseStaticFiles();
105116

106-
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
107-
app.UseRequestLocalization(locOptions.Value);
108117

118+
app.UseRequestLocalization(locOptions.Value);
119+
109120
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
121+
110122

111123
app.UseMvc(routes =>
112124
{
125+
routes.MapRoute(
126+
name: "default-localized",
127+
template: "{culture}/{controller}/{action}/{id?}",
128+
defaults: new { controller = "Home", action = "Index" },
129+
constraints: new { culture = new CultureSegmentRouteConstraint() }
130+
);
131+
132+
113133
routes.MapRoute(
114134
name: "default",
115135
template: "{controller=Home}/{action=Index}/{id?}");
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.0</TargetFramework>
4+
<TargetFramework>netcoreapp2.1</TargetFramework>
55
<PreserveCompilationContext>true</PreserveCompilationContext>
6-
<AssemblyName>localization.WebApp</AssemblyName>
7-
<OutputType>Exe</OutputType>
8-
<PackageId>localization.WebApp</PackageId>
6+
97
<UserSecretsId>aspnet-localization.WebApp-940cc8f9-71c6-4716-a22f-dac5c78eb805</UserSecretsId>
108
</PropertyGroup>
119

@@ -21,15 +19,9 @@
2119
</ItemGroup>
2220

2321
<ItemGroup>
24-
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
25-
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
26-
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
22+
<PackageReference Include="Microsoft.AspNetCore.App" />
2723
</ItemGroup>
2824

29-
<ItemGroup>
30-
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.*" />
31-
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.*" />
32-
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.*" />
33-
</ItemGroup>
25+
3426

3527
</Project>

src/localization.WebApp/package.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/localization.WebApp/web.config

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
3-
43
<!--
54
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
65
-->
7-
86
<system.webServer>
97
<handlers>
10-
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
8+
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
119
</handlers>
12-
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
10+
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false">
11+
<environmentVariables />
12+
</aspNetCore>
1313
</system.webServer>
14-
</configuration>
14+
</configuration>

0 commit comments

Comments
 (0)