Merge source code from aspnet/Localization

This commit is contained in:
Nate McMaster 2018-11-30 15:11:31 -08:00
commit 7e0f561425
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
63 changed files with 5985 additions and 0 deletions

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Product>Microsoft ASP.NET Core</Product>
<Description>Provides a request culture provider which gets culture and ui-culture from request's route data.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;localization</PackageTags>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization" />
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization.Routing
{
/// <summary>
/// Determines the culture information for a request via values in the route data.
/// </summary>
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
/// <summary>
/// The key that contains the culture name.
/// Defaults to "culture".
/// </summary>
public string RouteDataStringKey { get; set; } = "culture";
/// <summary>
/// The key that contains the UI culture name. If not specified or no value is found,
/// <see cref="RouteDataStringKey"/> will be used.
/// Defaults to "ui-culture".
/// </summary>
public string UIRouteDataStringKey { get; set; } = "ui-culture";
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
string culture = null;
string uiCulture = null;
if (!string.IsNullOrEmpty(RouteDataStringKey))
{
culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString();
}
if (!string.IsNullOrEmpty(UIRouteDataStringKey))
{
uiCulture = httpContext.GetRouteValue(UIRouteDataStringKey)?.ToString();
}
if (culture == null && uiCulture == null)
{
// No values specified for either so no match
return NullProviderCultureResult;
}
if (culture != null && uiCulture == null)
{
// Value for culture but not for UI culture so default to culture value for both
uiCulture = culture;
}
if (culture == null && uiCulture != null)
{
// Value for UI culture but not for culture so default to UI culture value for both
culture = uiCulture;
}
var providerResultCulture = new ProviderCultureResult(culture, uiCulture);
return Task.FromResult(providerResultCulture);
}
}
}

View File

@ -0,0 +1,80 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Localization.Routing, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Localization.Routing.RouteDataRequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_RouteDataStringKey",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_RouteDataStringKey",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_UIRouteDataStringKey",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_UIRouteDataStringKey",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization.Routing" />
<Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,195 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Localization.Routing
{
public class RouteDataRequestCultureProviderTest
{
[Theory]
[InlineData("{culture}/{ui-culture}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{CULTURE}/{UI-CULTURE}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{culture}/{ui-culture}/hello", "unsupported/unsupported/hello", "en-US", "en-US")]
[InlineData("{culture}/hello", "ar-SA/hello", "ar-SA", "en-US")]
[InlineData("hello", "hello", "en-US", "en-US")]
[InlineData("{ui-culture}/hello", "ar-YE/hello", "en-US", "ar-YE")]
public async Task GetCultureInfo_FromRouteData(
string routeTemplate,
string requestUrl,
string expectedCulture,
string expectedUICulture)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRouter(routes =>
{
routes.MapMiddlewareRoute(routeTemplate, fork =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options
}
};
fork.UseRequestLocalization(options);
fork.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
});
})
.ConfigureServices(services =>
{
services.AddRouting();
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync(requestUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal($"{expectedCulture},{expectedUICulture}", data);
}
}
[Fact]
public async Task GetDefaultCultureInfo_IfCultureKeysAreMissing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options
}
};
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal("en-US,en-US", data);
}
}
[Theory]
[InlineData("{c}/{uic}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{C}/{UIC}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{c}/hello", "ar-SA/hello", "ar-SA", "en-US")]
[InlineData("hello", "hello", "en-US", "en-US")]
[InlineData("{uic}/hello", "ar-YE/hello", "en-US", "ar-YE")]
public async Task GetCultureInfo_FromRouteData_WithCustomKeys(
string routeTemplate,
string requestUrl,
string expectedCulture,
string expectedUICulture)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRouter(routes =>
{
routes.MapMiddlewareRoute(routeTemplate, fork =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options,
RouteDataStringKey = "c",
UIRouteDataStringKey = "uic"
}
};
fork.UseRequestLocalization(options);
fork.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
});
})
.ConfigureServices(services =>
{
services.AddRouting();
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync(requestUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal($"{expectedCulture},{expectedUICulture}", data);
}
}
}
}

View File

@ -0,0 +1,24 @@
Localization
============
### Localization Samples
Here are a few samples that demonstrate different localization features including: localized views, localized strings in data annotations, creating custom localization resources ... etc.
* [Localization.StarterWeb](https://github.com/aspnet/Entropy/tree/dev/samples/Localization.StarterWeb) - comprehensive localization sample demonstrates almost all of the localization features
* [Localization.EntityFramework](https://github.com/aspnet/Entropy/tree/dev/samples/Localization.EntityFramework) - localization sample that uses an EntityFramework based localization provider for resources
* [Localization.CustomResourceManager](https://github.com/aspnet/Entropy/tree/dev/samples/Localization.CustomResourceManager) - localization sample that uses a custom `ResourceManagerStringLocalizer`
### Localization Providers
Community projects adapt _RequestCultureProvider_ for determining the culture information of an `HttpRequest`.
* [My.AspNetCore.Localization.Json](https://github.com/hishamco/My.AspNetCore.Localization.Json) - determines the culture information for a request from a JSON file.
* [My.AspNetCore.Localization.Session](https://github.com/hishamco/My.AspNetCore.Localization.Session) - determines the culture information for a request via values in the session state.
### Localization Resources
Community projects adapt _IStringLocalizer_ for fetching the localiztion resources.
* [My.Extensions.Localization.Json](https://github.com/hishamco/My.Extensions.Localization.Json) - fetches the localization resources from JSON file(s).
* [OrchardCore.Localization.PortableObject](https://github.com/OrchardCMS/OrchardCore/tree/dev/src/OrchardCore/OrchardCore.Localization.Core/PortableObject) - fetches the localization resources from PO file(s).

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
<Reference Include="Microsoft.Extensions.Localization" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Hola</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>こんにちは</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>您好</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>您好</value>
</data>
</root>

View File

@ -0,0 +1,155 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationSample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "My/Resources");
}
public void Configure(IApplicationBuilder app, IStringLocalizer<Startup> SR)
{
var supportedCultures = new [] { "en-US", "en-AU", "en-GB", "es-ES", "ja-JP", "fr-FR", "zh", "zh-CN" };
app.UseRequestLocalization(options =>
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
.SetDefaultCulture(supportedCultures[0])
// Optionally create an app-specific provider with just a delegate, e.g. look up user preference from DB.
// Inserting it as position 0 ensures it has priority over any of the default providers.
//.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
//{
//}));
);
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.EndsWith("favicon.ico"))
{
// Pesky browsers
context.Response.StatusCode = 404;
return;
}
context.Response.StatusCode = 200;
context.Response.ContentType = "text/html; charset=utf-8";
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
await context.Response.WriteAsync(
$@"<!doctype html>
<html>
<head>
<title>{SR["Request Localization"]}</title>
<style>
body {{ font-family: 'Segoe UI', Helvetica, Sans-Serif }}
h1, h2, h3, h4, th {{ font-family: 'Segoe UI Light', Helvetica, Sans-Serif }}
th {{ text-align: left }}
</style>
<script>
function useCookie() {{
var culture = document.getElementById('culture');
var uiCulture = document.getElementById('uiCulture');
var cookieValue = '{CookieRequestCultureProvider.DefaultCookieName}=c='+culture.options[culture.selectedIndex].value+'|uic='+uiCulture.options[uiCulture.selectedIndex].value;
document.cookie = cookieValue;
window.location = window.location.href.split('?')[0];
}}
function clearCookie() {{
document.cookie='{CookieRequestCultureProvider.DefaultCookieName}=""""';
}}
</script>
</head>
<body>");
await context.Response.WriteAsync($"<h1>{SR["Request Localization Sample"]}</h1>");
await context.Response.WriteAsync($"<h1>{SR["Hello"]}</h1>");
await context.Response.WriteAsync("<form id=\"theForm\" method=\"get\">");
await context.Response.WriteAsync($"<label for=\"culture\">{SR["Culture"]}: </label>");
await context.Response.WriteAsync("<select id=\"culture\" name=\"culture\">");
await WriteCultureSelectOptions(context);
await context.Response.WriteAsync("</select><br />");
await context.Response.WriteAsync($"<label for=\"uiCulture\">{SR["UI Culture"]}: </label>");
await context.Response.WriteAsync("<select id=\"uiCulture\" name=\"ui-culture\">");
await WriteCultureSelectOptions(context);
await context.Response.WriteAsync("</select><br />");
await context.Response.WriteAsync("<input type=\"submit\" value=\"go QS\" /> ");
await context.Response.WriteAsync($"<input type=\"button\" value=\"go cookie\" onclick='useCookie();' /> ");
await context.Response.WriteAsync($"<a href=\"/\" onclick='clearCookie();'>{SR["reset"]}</a>");
await context.Response.WriteAsync("</form>");
await context.Response.WriteAsync("<br />");
await context.Response.WriteAsync("<table><tbody>");
await context.Response.WriteAsync($"<tr><th>Winning provider:</th><td>{requestCultureFeature.Provider.GetType().Name}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current request culture:"]}</th><td>{requestCulture.Culture.DisplayName} ({requestCulture.Culture})</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current request UI culture:"]}</th><td>{requestCulture.UICulture.DisplayName} ({requestCulture.UICulture})</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current thread culture:"]}</th><td>{CultureInfo.CurrentCulture.DisplayName} ({CultureInfo.CurrentCulture})</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current thread UI culture:"]}</th><td>{CultureInfo.CurrentUICulture.DisplayName} ({CultureInfo.CurrentUICulture})</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current date (invariant full):"]}</th><td>{DateTime.Now.ToString("F", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current date (invariant):"]}</th><td>{DateTime.Now.ToString(CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current date (request full):"]}</th><td>{DateTime.Now.ToString("F")}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current date (request):"]}</th><td>{DateTime.Now.ToString()}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current time (invariant):"]}</th><td>{DateTime.Now.ToString("T", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Current time (request):"]}</th><td>{DateTime.Now.ToString("T")}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Big number (invariant):"]}</th><td>{(Math.Pow(2, 42) + 0.42).ToString("N", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Big number (request):"]}</th><td>{(Math.Pow(2, 42) + 0.42).ToString("N")}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Big number negative (invariant):"]}</th><td>{(-Math.Pow(2, 42) + 0.42).ToString("N", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Big number negative (request):"]}</th><td>{(-Math.Pow(2, 42) + 0.42).ToString("N")}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Money (invariant):"]}</th><td>{2199.50.ToString("C", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Money (request):"]}</th><td>{2199.50.ToString("C")}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Money negative (invariant):"]}</th><td>{(-2199.50).ToString("C", CultureInfo.InvariantCulture)}</td></tr>");
await context.Response.WriteAsync($"<tr><th>{SR["Money negative (request):"]}</th><td>{(-2199.50).ToString("C")}</td></tr>");
await context.Response.WriteAsync("</tbody></table>");
await context.Response.WriteAsync(
@"</body>
</html>");
});
}
private static async System.Threading.Tasks.Task WriteCultureSelectOptions(HttpContext context)
{
await context.Response.WriteAsync($" <option value=\"\">-- select --</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("en-US").Name}\">{new CultureInfo("en-US").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("en-AU").Name}\">{new CultureInfo("en-AU").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("en-GB").Name}\">{new CultureInfo("en-GB").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("fr-FR").Name}\">{new CultureInfo("fr-FR").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("es-ES").Name}\">{new CultureInfo("es-ES").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("ja-JP").Name}\">{new CultureInfo("ja-JP").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("zh").Name}\">{new CultureInfo("zh").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("zh-CN").Name}\">{new CultureInfo("zh-CN").DisplayName}</option>");
await context.Response.WriteAsync($" <option value=\"en-NOTREAL\">English (Not a real locale)</option>");
await context.Response.WriteAsync($" <option value=\"pp-NOTREAL\">Made-up (Not a real anything)</option>");
}
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.ConfigureLogging(factory => factory.AddConsole())
.UseKestrel()
.UseConfiguration(config)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Determines the culture information for a request via the value of the Accept-Language header.
/// </summary>
public class AcceptLanguageHeaderRequestCultureProvider : RequestCultureProvider
{
/// <summary>
/// The maximum number of values in the Accept-Language header to attempt to create a <see cref="System.Globalization.CultureInfo"/>
/// from for the current request.
/// Defaults to <c>3</c>.
/// </summary>
public int MaximumAcceptLanguageHeaderValuesToTry { get; set; } = 3;
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var acceptLanguageHeader = httpContext.Request.GetTypedHeaders().AcceptLanguage;
if (acceptLanguageHeader == null || acceptLanguageHeader.Count == 0)
{
return NullProviderCultureResult;
}
var languages = acceptLanguageHeader.AsEnumerable();
if (MaximumAcceptLanguageHeaderValuesToTry > 0)
{
// We take only the first configured number of languages from the header and then order those that we
// attempt to parse as a CultureInfo to mitigate potentially spinning CPU on lots of parse attempts.
languages = languages.Take(MaximumAcceptLanguageHeaderValuesToTry);
}
var orderedLanguages = languages.OrderByDescending(h => h, StringWithQualityHeaderValueComparer.QualityComparer)
.Select(x => x.Value).ToList();
if (orderedLanguages.Count > 0)
{
return Task.FromResult(new ProviderCultureResult(orderedLanguages));
}
return NullProviderCultureResult;
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for adding the <see cref="RequestLocalizationMiddleware"/> to an application.
/// </summary>
public static class ApplicationBuilderExtensions
{
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<RequestLocalizationMiddleware>();
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">The <see cref="RequestLocalizationOptions"/> to configure the middleware with.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(
this IApplicationBuilder app,
RequestLocalizationOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="optionsAction"></param>
/// <remarks>
/// This will going to instantiate a new <see cref="RequestLocalizationOptions"/> that doesn't come from the services.
/// </remarks>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(
this IApplicationBuilder app,
Action<RequestLocalizationOptions> optionsAction)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (optionsAction == null)
{
throw new ArgumentNullException(nameof(optionsAction));
}
var options = new RequestLocalizationOptions();
optionsAction.Invoke(options);
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="cultures">The culture names to be added by the application, which is represents both supported cultures and UI cultures.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// Note that the first culture is the default culture name.
/// </remarks>
public static IApplicationBuilder UseRequestLocalization(
this IApplicationBuilder app,
params string[] cultures)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (cultures == null)
{
throw new ArgumentNullException(nameof(cultures));
}
if (cultures.Length == 0)
{
throw new ArgumentException(Resources.Exception_CulturesShouldNotBeEmpty);
}
var options = new RequestLocalizationOptions()
.AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures)
.SetDefaultCulture(cultures[0]);
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Determines the culture information for a request via the value of a cookie.
/// </summary>
public class CookieRequestCultureProvider : RequestCultureProvider
{
private static readonly char[] _cookieSeparator = new[] { '|' };
private static readonly string _culturePrefix = "c=";
private static readonly string _uiCulturePrefix = "uic=";
/// <summary>
/// Represent the default cookie name used to track the user's preferred culture information, which is ".AspNetCore.Culture".
/// </summary>
public static readonly string DefaultCookieName = ".AspNetCore.Culture";
/// <summary>
/// The name of the cookie that contains the user's preferred culture information.
/// Defaults to <see cref="DefaultCookieName"/>.
/// </summary>
public string CookieName { get; set; } = DefaultCookieName;
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var cookie = httpContext.Request.Cookies[CookieName];
if (string.IsNullOrEmpty(cookie))
{
return NullProviderCultureResult;
}
var providerResultCulture = ParseCookieValue(cookie);
return Task.FromResult(providerResultCulture);
}
/// <summary>
/// Creates a string representation of a <see cref="RequestCulture"/> for placement in a cookie.
/// </summary>
/// <param name="requestCulture">The <see cref="RequestCulture"/>.</param>
/// <returns>The cookie value.</returns>
public static string MakeCookieValue(RequestCulture requestCulture)
{
if (requestCulture == null)
{
throw new ArgumentNullException(nameof(requestCulture));
}
var seperator = _cookieSeparator[0].ToString();
return string.Join(seperator,
$"{_culturePrefix}{requestCulture.Culture.Name}",
$"{_uiCulturePrefix}{requestCulture.UICulture.Name}");
}
/// <summary>
/// Parses a <see cref="RequestCulture"/> from the specified cookie value.
/// Returns <c>null</c> if parsing fails.
/// </summary>
/// <param name="value">The cookie value to parse.</param>
/// <returns>The <see cref="RequestCulture"/> or <c>null</c> if parsing fails.</returns>
public static ProviderCultureResult ParseCookieValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var parts = value.Split(_cookieSeparator, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
{
return null;
}
var potentialCultureName = parts[0];
var potentialUICultureName = parts[1];
if (!potentialCultureName.StartsWith(_culturePrefix) || !potentialUICultureName.StartsWith(_uiCulturePrefix))
{
return null;
}
var cultureName = potentialCultureName.Substring(_culturePrefix.Length);
var uiCultureName = potentialUICultureName.Substring(_uiCulturePrefix.Length);
if (cultureName == null && uiCultureName == null)
{
// No values specified for either so no match
return null;
}
if (cultureName != null && uiCultureName == null)
{
// Value for culture but not for UI culture so default to culture value for both
uiCultureName = cultureName;
}
if (cultureName == null && uiCultureName != null)
{
// Value for UI culture but not for culture so default to UI culture value for both
cultureName = uiCultureName;
}
return new ProviderCultureResult(cultureName, uiCultureName);
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Determines the culture information for a request via the configured delegate.
/// </summary>
public class CustomRequestCultureProvider : RequestCultureProvider
{
private readonly Func<HttpContext, Task<ProviderCultureResult>> _provider;
/// <summary>
/// Creates a new <see cref="CustomRequestCultureProvider"/> using the specified delegate.
/// </summary>
/// <param name="provider">The provider delegate.</param>
public CustomRequestCultureProvider(Func<HttpContext, Task<ProviderCultureResult>> provider)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
_provider = provider;
}
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
return _provider(httpContext);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Represents the feature that provides the current request's culture information.
/// </summary>
public interface IRequestCultureFeature
{
/// <summary>
/// The <see cref="Localization.RequestCulture"/> of the request.
/// </summary>
RequestCulture RequestCulture { get; }
/// <summary>
/// The <see cref="IRequestCultureProvider"/> that determined the request's culture information.
/// If the value is <c>null</c> then no provider was used and the request's culture was set to the value of
/// <see cref="Builder.RequestLocalizationOptions.DefaultRequestCulture"/>.
/// </summary>
IRequestCultureProvider Provider { get; }
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Represents a provider for determining the culture information of an <see cref="HttpRequest"/>.
/// </summary>
public interface IRequestCultureProvider
{
/// <summary>
/// Implements the provider to determine the culture of the given request.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> for the request.</param>
/// <returns>
/// The determined <see cref="ProviderCultureResult"/>.
/// Returns <c>null</c> if the provider couldn't determine a <see cref="ProviderCultureResult"/>.
/// </returns>
Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext);
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Localization.Internal
{
internal static class RequestCultureProviderLoggerExtensions
{
private static readonly Action<ILogger, string, IList<StringSegment>, Exception> _unsupportedCulture;
private static readonly Action<ILogger, string, IList<StringSegment>, Exception> _unsupportedUICulture;
static RequestCultureProviderLoggerExtensions()
{
_unsupportedCulture = LoggerMessage.Define<string, IList<StringSegment>>(
LogLevel.Warning,
1,
"{requestCultureProvider} returned the following unsupported cultures '{cultures}'.");
_unsupportedUICulture = LoggerMessage.Define<string, IList<StringSegment>>(
LogLevel.Warning,
2,
"{requestCultureProvider} returned the following unsupported UI Cultures '{uiCultures}'.");
}
public static void UnsupportedCultures(this ILogger logger, string requestCultureProvider, IList<StringSegment> cultures)
{
_unsupportedCulture(logger, requestCultureProvider, cultures, null);
}
public static void UnsupportedUICultures(this ILogger logger, string requestCultureProvider, IList<StringSegment> uiCultures)
{
_unsupportedUICulture(logger, requestCultureProvider, uiCultures, null);
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Product>Microsoft ASP.NET Core</Product>
<Description>ASP.NET Core middleware for automatically applying culture information to HTTP requests. Culture information can be specified in the HTTP header, query string, cookie, or custom source.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;localization</PackageTags>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
<Reference Include="Microsoft.Extensions.Localization.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,37 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Localization
{
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Please provide at least one culture.
/// </summary>
internal static string Exception_CulturesShouldNotBeEmpty
{
get { return GetString("Exception_CulturesShouldNotBeEmpty"); }
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Details about the cultures obtained from <see cref="IRequestCultureProvider"/>.
/// </summary>
public class ProviderCultureResult
{
/// <summary>
/// Creates a new <see cref="ProviderCultureResult"/> object that has its <see cref="Cultures"/> and
/// <see cref="UICultures"/> properties set to the same culture value.
/// </summary>
/// <param name="culture">The name of the culture to be used for formatting, text, i.e. language.</param>
public ProviderCultureResult(StringSegment culture)
: this(new List<StringSegment> { culture }, new List<StringSegment> { culture })
{
}
/// <summary>
/// Creates a new <see cref="ProviderCultureResult"/> object has its <see cref="Cultures"/> and
/// <see cref="UICultures"/> properties set to the respective culture values provided.
/// </summary>
/// <param name="culture">The name of the culture to be used for formatting.</param>
/// <param name="uiCulture"> The name of the ui culture to be used for text, i.e. language.</param>
public ProviderCultureResult(StringSegment culture, StringSegment uiCulture)
: this(new List<StringSegment> { culture }, new List<StringSegment> { uiCulture })
{
}
/// <summary>
/// Creates a new <see cref="ProviderCultureResult"/> object that has its <see cref="Cultures"/> and
/// <see cref="UICultures"/> properties set to the same culture value.
/// </summary>
/// <param name="cultures">The list of cultures to be used for formatting, text, i.e. language.</param>
public ProviderCultureResult(IList<StringSegment> cultures)
: this(cultures, cultures)
{
}
/// <summary>
/// Creates a new <see cref="ProviderCultureResult"/> object has its <see cref="Cultures"/> and
/// <see cref="UICultures"/> properties set to the respective culture values provided.
/// </summary>
/// <param name="cultures">The list of cultures to be used for formatting.</param>
/// <param name="uiCultures">The list of ui cultures to be used for text, i.e. language.</param>
public ProviderCultureResult(IList<StringSegment> cultures, IList<StringSegment> uiCultures)
{
Cultures = cultures;
UICultures = uiCultures;
}
/// <summary>
/// Gets the list of cultures to be used for formatting.
/// </summary>
public IList<StringSegment> Cultures { get; }
/// <summary>
/// Gets the list of ui cultures to be used for text, i.e. language;
/// </summary>
public IList<StringSegment> UICultures { get; }
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Determines the culture information for a request via values in the query string.
/// </summary>
public class QueryStringRequestCultureProvider : RequestCultureProvider
{
/// <summary>
/// The key that contains the culture name.
/// Defaults to "culture".
/// </summary>
public string QueryStringKey { get; set; } = "culture";
/// <summary>
/// The key that contains the UI culture name. If not specified or no value is found,
/// <see cref="QueryStringKey"/> will be used.
/// Defaults to "ui-culture".
/// </summary>
public string UIQueryStringKey { get; set; } = "ui-culture";
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var request = httpContext.Request;
if (!request.QueryString.HasValue)
{
return NullProviderCultureResult;
}
string queryCulture = null;
string queryUICulture = null;
if (!string.IsNullOrWhiteSpace(QueryStringKey))
{
queryCulture = request.Query[QueryStringKey];
}
if (!string.IsNullOrWhiteSpace(UIQueryStringKey))
{
queryUICulture = request.Query[UIQueryStringKey];
}
if (queryCulture == null && queryUICulture == null)
{
// No values specified for either so no match
return NullProviderCultureResult;
}
if (queryCulture != null && queryUICulture == null)
{
// Value for culture but not for UI culture so default to culture value for both
queryUICulture = queryCulture;
}
if (queryCulture == null && queryUICulture != null)
{
// Value for UI culture but not for culture so default to UI culture value for both
queryCulture = queryUICulture;
}
var providerResultCulture = new ProviderCultureResult(queryCulture, queryUICulture);
return Task.FromResult(providerResultCulture);
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Details about the culture for an <see cref="Http.HttpRequest"/>.
/// </summary>
public class RequestCulture
{
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the same <see cref="CultureInfo"/> value.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request.</param>
public RequestCulture(CultureInfo culture)
: this(culture, culture)
{
}
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the same <see cref="CultureInfo"/> value.
/// </summary>
/// <param name="culture">The culture for the request.</param>
public RequestCulture(string culture)
: this(culture, culture)
{
}
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the respective <see cref="CultureInfo"/> values provided.
/// </summary>
/// <param name="culture">The culture for the request to be used for formatting.</param>
/// <param name="uiCulture">The culture for the request to be used for text, i.e. language.</param>
public RequestCulture(string culture, string uiCulture)
: this (new CultureInfo(culture), new CultureInfo(uiCulture))
{
}
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the respective <see cref="CultureInfo"/> values provided.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request to be used for formatting.</param>
/// <param name="uiCulture">The <see cref="CultureInfo"/> for the request to be used for text, i.e. language.</param>
public RequestCulture(CultureInfo culture, CultureInfo uiCulture)
{
if (culture == null)
{
throw new ArgumentNullException(nameof(culture));
}
if (uiCulture == null)
{
throw new ArgumentNullException(nameof(uiCulture));
}
Culture = culture;
UICulture = uiCulture;
}
/// <summary>
/// Gets the <see cref="CultureInfo"/> for the request to be used for formatting.
/// </summary>
public CultureInfo Culture { get; }
/// <summary>
/// Gets the <see cref="CultureInfo"/> for the request to be used for text, i.e. language;
/// </summary>
public CultureInfo UICulture { get; }
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Provides the current request's culture information.
/// </summary>
public class RequestCultureFeature : IRequestCultureFeature
{
/// <summary>
/// Creates a new <see cref="RequestCultureFeature"/> with the specified <see cref="Localization.RequestCulture"/>.
/// </summary>
/// <param name="requestCulture">The <see cref="Localization.RequestCulture"/>.</param>
/// <param name="provider">The <see cref="IRequestCultureProvider"/>.</param>
public RequestCultureFeature(RequestCulture requestCulture, IRequestCultureProvider provider)
{
if (requestCulture == null)
{
throw new ArgumentNullException(nameof(requestCulture));
}
RequestCulture = requestCulture;
Provider = provider;
}
/// <inheritdoc />
public RequestCulture RequestCulture { get; }
/// <inheritdoc />
public IRequestCultureProvider Provider { get; }
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// An abstract base class provider for determining the culture information of an <see cref="HttpRequest"/>.
/// </summary>
public abstract class RequestCultureProvider : IRequestCultureProvider
{
/// <summary>
/// Result that indicates that this instance of <see cref="RequestCultureProvider" /> could not determine the
/// request culture.
/// </summary>
protected static readonly Task<ProviderCultureResult> NullProviderCultureResult = Task.FromResult(default(ProviderCultureResult));
/// <summary>
/// The current options for the <see cref="RequestLocalizationMiddleware"/>.
/// </summary>
public RequestLocalizationOptions Options { get; set; }
/// <inheritdoc />
public abstract Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext);
}
}

View File

@ -0,0 +1,215 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Localization
{
/// <summary>
/// Enables automatic setting of the culture for <see cref="HttpRequest"/>s based on information
/// sent by the client in headers and logic provided by the application.
/// </summary>
public class RequestLocalizationMiddleware
{
private static readonly int MaxCultureFallbackDepth = 5;
private readonly RequestDelegate _next;
private readonly RequestLocalizationOptions _options;
private ILogger _logger;
/// <summary>
/// Creates a new <see cref="RequestLocalizationMiddleware"/>.
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
/// <param name="options">The <see cref="RequestLocalizationOptions"/> representing the options for the
/// <see cref="RequestLocalizationMiddleware"/>.</param>
public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next ?? throw new ArgumentNullException(nameof(next));
_options = options.Value;
}
/// <summary>
/// Invokes the logic of the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var requestCulture = _options.DefaultRequestCulture;
IRequestCultureProvider winningProvider = null;
if (_options.RequestCultureProviders != null)
{
foreach (var provider in _options.RequestCultureProviders)
{
var providerResultCulture = await provider.DetermineProviderCultureResult(context);
if (providerResultCulture == null)
{
continue;
}
var cultures = providerResultCulture.Cultures;
var uiCultures = providerResultCulture.UICultures;
CultureInfo cultureInfo = null;
CultureInfo uiCultureInfo = null;
if (_options.SupportedCultures != null)
{
cultureInfo = GetCultureInfo(
cultures,
_options.SupportedCultures,
_options.FallBackToParentCultures);
if (cultureInfo == null)
{
EnsureLogger(context);
_logger?.UnsupportedCultures(provider.GetType().Name, cultures);
}
}
if (_options.SupportedUICultures != null)
{
uiCultureInfo = GetCultureInfo(
uiCultures,
_options.SupportedUICultures,
_options.FallBackToParentUICultures);
if (uiCultureInfo == null)
{
EnsureLogger(context);
_logger?.UnsupportedUICultures(provider.GetType().Name, uiCultures);
}
}
if (cultureInfo == null && uiCultureInfo == null)
{
continue;
}
if (cultureInfo == null && uiCultureInfo != null)
{
cultureInfo = _options.DefaultRequestCulture.Culture;
}
if (cultureInfo != null && uiCultureInfo == null)
{
uiCultureInfo = _options.DefaultRequestCulture.UICulture;
}
var result = new RequestCulture(cultureInfo, uiCultureInfo);
if (result != null)
{
requestCulture = result;
winningProvider = provider;
break;
}
}
}
context.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(requestCulture, winningProvider));
SetCurrentThreadCulture(requestCulture);
await _next(context);
}
private void EnsureLogger(HttpContext context)
{
_logger = _logger ?? context.RequestServices.GetService<ILogger<RequestLocalizationMiddleware>>();
}
private static void SetCurrentThreadCulture(RequestCulture requestCulture)
{
CultureInfo.CurrentCulture = requestCulture.Culture;
CultureInfo.CurrentUICulture = requestCulture.UICulture;
}
private static CultureInfo GetCultureInfo(
IList<StringSegment> cultureNames,
IList<CultureInfo> supportedCultures,
bool fallbackToParentCultures)
{
foreach (var cultureName in cultureNames)
{
// Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
// the CultureInfo ctor
if (cultureName != null)
{
var cultureInfo = GetCultureInfo(cultureName, supportedCultures, fallbackToParentCultures, currentDepth: 0);
if (cultureInfo != null)
{
return cultureInfo;
}
}
}
return null;
}
private static CultureInfo GetCultureInfo(StringSegment name, IList<CultureInfo> supportedCultures)
{
// Allow only known culture names as this API is called with input from users (HTTP requests) and
// creating CultureInfo objects is expensive and we don't want it to throw either.
if (name == null || supportedCultures == null)
{
return null;
}
var culture = supportedCultures.FirstOrDefault(
supportedCulture => StringSegment.Equals(supportedCulture.Name, name, StringComparison.OrdinalIgnoreCase));
if (culture == null)
{
return null;
}
return CultureInfo.ReadOnly(culture);
}
private static CultureInfo GetCultureInfo(
StringSegment cultureName,
IList<CultureInfo> supportedCultures,
bool fallbackToParentCultures,
int currentDepth)
{
var culture = GetCultureInfo(cultureName, supportedCultures);
if (culture == null && fallbackToParentCultures && currentDepth < MaxCultureFallbackDepth)
{
var lastIndexOfHyphen = cultureName.LastIndexOf('-');
if (lastIndexOfHyphen > 0)
{
// Trim the trailing section from the culture name, e.g. "fr-FR" becomes "fr"
var parentCultureName = cultureName.Subsegment(0, lastIndexOfHyphen);
culture = GetCultureInfo(parentCultureName, supportedCultures, fallbackToParentCultures, currentDepth + 1);
}
}
return culture;
}
}
}

View File

@ -0,0 +1,160 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNetCore.Localization;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Specifies options for the <see cref="RequestLocalizationMiddleware"/>.
/// </summary>
public class RequestLocalizationOptions
{
private RequestCulture _defaultRequestCulture =
new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
/// <summary>
/// Creates a new <see cref="RequestLocalizationOptions"/> with default values.
/// </summary>
public RequestLocalizationOptions()
{
RequestCultureProviders = new List<IRequestCultureProvider>
{
new QueryStringRequestCultureProvider { Options = this },
new CookieRequestCultureProvider { Options = this },
new AcceptLanguageHeaderRequestCultureProvider { Options = this }
};
}
/// <summary>
/// Gets or sets the default culture to use for requests when a supported culture could not be determined by
/// one of the configured <see cref="IRequestCultureProvider"/>s.
/// Defaults to <see cref="CultureInfo.CurrentCulture"/> and <see cref="CultureInfo.CurrentUICulture"/>.
/// </summary>
public RequestCulture DefaultRequestCulture
{
get
{
return _defaultRequestCulture;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_defaultRequestCulture = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether to set a request culture to an parent culture in the case the
/// culture determined by the configured <see cref="IRequestCultureProvider"/>s is not in the
/// <see cref="SupportedCultures"/> list but a parent culture is.
/// Defaults to <c>true</c>;
/// </summary>
/// <remarks>
/// Note that the parent culture check is done using only the culture name.
/// </remarks>
/// <example>
/// If this property is <c>true</c> and the application is configured to support the culture "fr", but not the
/// culture "fr-FR", and a configured <see cref="IRequestCultureProvider"/> determines a request's culture is
/// "fr-FR", then the request's culture will be set to the culture "fr", as it is a parent of "fr-FR".
/// </example>
public bool FallBackToParentCultures { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether to set a request UI culture to a parent culture in the case the
/// UI culture determined by the configured <see cref="IRequestCultureProvider"/>s is not in the
/// <see cref="SupportedUICultures"/> list but a parent culture is.
/// Defaults to <c>true</c>;
/// </summary>
/// <remarks>
/// Note that the parent culture check is done using ony the culture name.
/// </remarks>
/// <example>
/// If this property is <c>true</c> and the application is configured to support the UI culture "fr", but not
/// the UI culture "fr-FR", and a configured <see cref="IRequestCultureProvider"/> determines a request's UI
/// culture is "fr-FR", then the request's UI culture will be set to the culture "fr", as it is a parent of
/// "fr-FR".
/// </example>
public bool FallBackToParentUICultures { get; set; } = true;
/// <summary>
/// The cultures supported by the application. The <see cref="RequestLocalizationMiddleware"/> will only set
/// the current request culture to an entry in this list.
/// Defaults to <see cref="CultureInfo.CurrentCulture"/>.
/// </summary>
public IList<CultureInfo> SupportedCultures { get; set; } = new List<CultureInfo> { CultureInfo.CurrentCulture };
/// <summary>
/// The UI cultures supported by the application. The <see cref="RequestLocalizationMiddleware"/> will only set
/// the current request culture to an entry in this list.
/// Defaults to <see cref="CultureInfo.CurrentUICulture"/>.
/// </summary>
public IList<CultureInfo> SupportedUICultures { get; set; } = new List<CultureInfo> { CultureInfo.CurrentUICulture };
/// <summary>
/// An ordered list of providers used to determine a request's culture information. The first provider that
/// returns a non-<c>null</c> result for a given request will be used.
/// Defaults to the following:
/// <list type="number">
/// <item><description><see cref="QueryStringRequestCultureProvider"/></description></item>
/// <item><description><see cref="CookieRequestCultureProvider"/></description></item>
/// <item><description><see cref="AcceptLanguageHeaderRequestCultureProvider"/></description></item>
/// </list>
/// </summary>
public IList<IRequestCultureProvider> RequestCultureProviders { get; set; }
/// <summary>
/// Adds the set of the supported cultures by the application.
/// </summary>
/// <param name="cultures">The cultures to be added.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions AddSupportedCultures(params string[] cultures)
{
var supportedCultures = new List<CultureInfo>();
foreach (var culture in cultures)
{
supportedCultures.Add(new CultureInfo(culture));
}
SupportedCultures = supportedCultures;
return this;
}
/// <summary>
/// Adds the set of the supported UI cultures by the application.
/// </summary>
/// <param name="uiCultures">The UI cultures to be added.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions AddSupportedUICultures(params string[] uiCultures)
{
var supportedUICultures = new List<CultureInfo>();
foreach (var culture in uiCultures)
{
supportedUICultures.Add(new CultureInfo(culture));
}
SupportedUICultures = supportedUICultures;
return this;
}
/// <summary>
/// Set the default culture which is used by the application when a supported culture could not be determined by
/// one of the configured <see cref="IRequestCultureProvider"/>s.
/// </summary>
/// <param name="defaultCulture">The default culture to be set.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions SetDefaultCulture(string defaultCulture)
{
DefaultRequestCulture = new RequestCulture(defaultCulture);
return this;
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_CulturesShouldNotBeEmpty" xml:space="preserve">
<value>Please provide at least one culture.</value>
</data>
</root>

View File

@ -0,0 +1,893 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Builder.ApplicationBuilderExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseRequestLocalization",
"Parameters": [
{
"Name": "app",
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseRequestLocalization",
"Parameters": [
{
"Name": "app",
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
},
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions"
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseRequestLocalization",
"Parameters": [
{
"Name": "app",
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
},
{
"Name": "optionsAction",
"Type": "System.Action<Microsoft.AspNetCore.Builder.RequestLocalizationOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseRequestLocalization",
"Parameters": [
{
"Name": "app",
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
},
{
"Name": "cultures",
"Type": "System.String[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_DefaultRequestCulture",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Localization.RequestCulture",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_DefaultRequestCulture",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Localization.RequestCulture"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_FallBackToParentCultures",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_FallBackToParentCultures",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_FallBackToParentUICultures",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_FallBackToParentUICultures",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_SupportedCultures",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IList<System.Globalization.CultureInfo>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_SupportedCultures",
"Parameters": [
{
"Name": "value",
"Type": "System.Collections.Generic.IList<System.Globalization.CultureInfo>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_SupportedUICultures",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IList<System.Globalization.CultureInfo>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_SupportedUICultures",
"Parameters": [
{
"Name": "value",
"Type": "System.Collections.Generic.IList<System.Globalization.CultureInfo>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_RequestCultureProviders",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IList<Microsoft.AspNetCore.Localization.IRequestCultureProvider>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_RequestCultureProviders",
"Parameters": [
{
"Name": "value",
"Type": "System.Collections.Generic.IList<Microsoft.AspNetCore.Localization.IRequestCultureProvider>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "AddSupportedCultures",
"Parameters": [
{
"Name": "cultures",
"Type": "System.String[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "AddSupportedUICultures",
"Parameters": [
{
"Name": "uiCultures",
"Type": "System.String[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "SetDefaultCulture",
"Parameters": [
{
"Name": "defaultCulture",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.AcceptLanguageHeaderRequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_MaximumAcceptLanguageHeaderValuesToTry",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaximumAcceptLanguageHeaderValuesToTry",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.CookieRequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_CookieName",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_CookieName",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "MakeCookieValue",
"Parameters": [
{
"Name": "requestCulture",
"Type": "Microsoft.AspNetCore.Localization.RequestCulture"
}
],
"ReturnType": "System.String",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "ParseCookieValue",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Localization.ProviderCultureResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Field",
"Name": "DefaultCookieName",
"Parameters": [],
"ReturnType": "System.String",
"Static": true,
"ReadOnly": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.CustomRequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "provider",
"Type": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.IRequestCultureFeature",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_RequestCulture",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Localization.RequestCulture",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Provider",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.ProviderCultureResult",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Cultures",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_UICultures",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "Microsoft.Extensions.Primitives.StringSegment"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "Microsoft.Extensions.Primitives.StringSegment"
},
{
"Name": "uiCulture",
"Type": "Microsoft.Extensions.Primitives.StringSegment"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "cultures",
"Type": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "cultures",
"Type": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>"
},
{
"Name": "uiCultures",
"Type": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.QueryStringRequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_QueryStringKey",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_QueryStringKey",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_UIQueryStringKey",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_UIQueryStringKey",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.RequestCulture",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Culture",
"Parameters": [],
"ReturnType": "System.Globalization.CultureInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_UICulture",
"Parameters": [],
"ReturnType": "System.Globalization.CultureInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "System.String"
},
{
"Name": "uiCulture",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
},
{
"Name": "uiCulture",
"Type": "System.Globalization.CultureInfo"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.RequestCultureFeature",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Localization.IRequestCultureFeature"
],
"Members": [
{
"Kind": "Method",
"Name": "get_RequestCulture",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Localization.RequestCulture",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureFeature",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Provider",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureFeature",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "requestCulture",
"Type": "Microsoft.AspNetCore.Localization.RequestCulture"
},
{
"Name": "provider",
"Type": "Microsoft.AspNetCore.Localization.IRequestCultureProvider"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.RequestCultureProvider",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Localization.IRequestCultureProvider"
],
"Members": [
{
"Kind": "Method",
"Name": "DetermineProviderCultureResult",
"Parameters": [
{
"Name": "httpContext",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Virtual": true,
"Abstract": true,
"ImplementedInterface": "Microsoft.AspNetCore.Localization.IRequestCultureProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Options",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_Options",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Builder.RequestLocalizationOptions"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Field",
"Name": "NullProviderCultureResult",
"Parameters": [],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Localization.ProviderCultureResult>",
"Static": true,
"ReadOnly": true,
"Visibility": "Protected",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Invoke",
"Parameters": [
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "next",
"Type": "Microsoft.AspNetCore.Http.RequestDelegate"
},
{
"Name": "options",
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.RequestLocalizationOptions>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using LocalizationSample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.AspNetCore.Localization.FunctionalTests
{
public class LocalizationSampleTest
{
[Fact]
public async Task LocalizationSampleSmokeTest()
{
// Arrange
var webHostBuilder = new WebHostBuilder().UseStartup(typeof(Startup));
var testHost = new TestServer(webHostBuilder);
var locale = "fr-FR";
var client = testHost.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "My/Resources");
var cookieValue = $"c={locale}|uic={locale}";
request.Headers.Add("Cookie", $"{CookieRequestCultureProvider.DefaultCookieName}={cookieValue}");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("<h1>Bonjour</h1>", await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -0,0 +1,105 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using LocalizationWebsite;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.AspNetCore.Localization.FunctionalTests
{
public class LocalizationTest
{
[Fact]
public Task Localization_CustomCulture()
{
return RunTest(
typeof(StartupCustomCulturePreserved),
"en-US",
"kr10.00");
}
[Fact]
public Task Localization_GetAllStrings()
{
return RunTest(
typeof(StartupGetAllStrings),
"fr-FR",
"1 Bonjour from Customer in resources folder");
}
[Fact]
public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue()
{
return RunTest(
typeof(StartupResourcesInClassLibrary),
"fr-FR",
"Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryWithAttribute Bonjour from ResourcesClassLibraryWithAttribute");
}
[Fact]
public Task Localization_ResourcesInFolder_ReturnLocalizedValue()
{
return RunTest(
typeof(StartupResourcesInFolder),
"fr-FR",
"Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Hello");
}
[Fact]
public Task Localization_ResourcesInFolder_ReturnLocalizedValue_WithCultureFallback()
{
return RunTest(
typeof(StartupResourcesInFolder),
"fr-FR-test",
"Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Hello");
}
[Fact]
public Task Localization_ResourcesInFolder_ReturnNonLocalizedValue_CultureHierarchyTooDeep()
{
return RunTest(
typeof(StartupResourcesInFolder),
"fr-FR-test-again-too-deep-to-work",
"Hello Hello Hello Hello");
}
[Fact]
public Task Localization_ResourcesAtRootFolder_ReturnLocalizedValue()
{
return RunTest(
typeof(StartupResourcesAtRootFolder),
"fr-FR",
"Bonjour from StartupResourcesAtRootFolder Bonjour from Test in root folder Bonjour from Customer in Models folder");
}
[Fact]
public Task Localization_BuilderAPIs()
{
return RunTest(
typeof(StartupBuilderAPIs),
"ar-YE",
"Hello");
}
private async Task RunTest(Type startupType, string culture, string expected)
{
var webHostBuilder = new WebHostBuilder().UseStartup(startupType);
var testHost = new TestServer(webHostBuilder);
var client = testHost.CreateClient();
var request = new HttpRequestMessage();
var cookieValue = $"c={culture}|uic={culture}";
request.Headers.Add("Cookie", $"{CookieRequestCultureProvider.DefaultCookieName}={cookieValue}");
var response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expected, await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\sample\LocalizationSample.csproj" />
<ProjectReference Include="..\..\testassets\LocalizationWebsite\LocalizationWebsite.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,157 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.Extensions.Localization
{
public class AcceptLanguageHeaderRequestCultureProviderTest
{
[Fact]
public async Task GetFallbackLanguage_ReturnsFirstNonNullCultureFromSupportedCultureList()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA"),
new CultureInfo("en-US")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("jp,ar-SA,en-US");
var count = client.DefaultRequestHeaders.AcceptLanguage.Count;
var response = await client.GetAsync(string.Empty);
Assert.Equal(3, count);
}
}
[Fact]
public async Task GetFallbackLanguage_ReturnsFromSupportedCulture_AcceptLanguageListContainsSupportedCultures()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("fr-FR"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA"),
new CultureInfo("en-US")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-GB,ar-SA,en-US");
var count = client.DefaultRequestHeaders.AcceptLanguage.Count;
var response = await client.GetAsync(string.Empty);
}
}
[Fact]
public async Task GetFallbackLanguage_ReturnsDefault_AcceptLanguageListDoesnotContainSupportedCultures()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("fr-FR"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA"),
new CultureInfo("af-ZA")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("fr-FR", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-GB,ar-MA,en-US");
var count = client.DefaultRequestHeaders.AcceptLanguage.Count;
var response = await client.GetAsync(string.Empty);
Assert.Equal(3, count);
}
}
[Fact]
public async Task OmitDefaultRequestCultureShouldNotThrowNullReferenceException_And_ShouldGetTheRightCulture()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-YE", requestCulture.Culture.Name);
Assert.Equal("ar-YE", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-GB,ar-YE,en-US");
var count = client.DefaultRequestHeaders.AcceptLanguage.Count;
var response = await client.GetAsync(string.Empty);
Assert.Equal(3, count);
}
}
}
}

View File

@ -0,0 +1,254 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.Extensions.Localization
{
public class CookieRequestCultureProviderTest
{
[Fact]
public async Task GetCultureInfoFromPersistentCookie()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
};
var provider = new CookieRequestCultureProvider
{
CookieName = "Preferences"
};
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var culture = new CultureInfo("ar-SA");
var requestCulture = new RequestCulture(culture);
var value = CookieRequestCultureProvider.MakeCookieValue(requestCulture);
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue("Preferences", value).ToString());
var response = await client.GetAsync(string.Empty);
Assert.Equal("c=ar-SA|uic=ar-SA", value);
}
}
[Fact]
public async Task GetDefaultCultureInfoIfCultureKeysAreMissingOrInvalid()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
};
var provider = new CookieRequestCultureProvider
{
CookieName = "Preferences"
};
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("en-US", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue("Preferences", "uic=ar-SA").ToString());
var response = await client.GetAsync(string.Empty);
}
}
[Fact]
public async Task GetDefaultCultureInfoIfCookieDoesNotExist()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
};
var provider = new CookieRequestCultureProvider
{
CookieName = "Preferences"
};
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("en-US", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
}
}
[Fact]
public async Task RequestLocalizationMiddleware_LogsWarningsForUnsupportedCultures()
{
var sink = new TestSink(
TestSink.EnableWithTypeName<RequestLocalizationMiddleware>,
TestSink.EnableWithTypeName<RequestLocalizationMiddleware>);
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
var provider = new CookieRequestCultureProvider
{
CookieName = "Preferences"
};
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context => Task.CompletedTask);
})
.ConfigureServices(services =>
{
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var culture = "??";
var uiCulture = "ar-YE";
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue("Preferences", $"c={culture}|uic={uiCulture}").ToString());
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
}
var expectedMessage = $"{nameof(CookieRequestCultureProvider)} returned the following unsupported cultures '??'.";
var write = Assert.Single(sink.Writes);
Assert.Equal(LogLevel.Warning, write.LogLevel);
Assert.Equal(expectedMessage, write.State.ToString());
}
[Fact]
public async Task RequestLocalizationMiddleware_LogsWarningsForUnsupportedUICultures()
{
var sink = new TestSink(
TestSink.EnableWithTypeName<RequestLocalizationMiddleware>,
TestSink.EnableWithTypeName<RequestLocalizationMiddleware>);
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
var provider = new CookieRequestCultureProvider
{
CookieName = "Preferences"
};
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context => Task.CompletedTask);
})
.ConfigureServices(services =>
{
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var culture = "ar-YE";
var uiCulture = "??";
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue("Preferences", $"c={culture}|uic={uiCulture}").ToString());
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
}
var expectedMessage = $"{nameof(CookieRequestCultureProvider)} returned the following unsupported UI Cultures '??'.";
var write = Assert.Single(sink.Writes);
Assert.Equal(LogLevel.Warning, write.LogLevel);
Assert.Equal(expectedMessage, write.State.ToString());
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.Extensions.Localization
{
public class CustomRequestCultureProviderTest
{
[Fact]
public async Task CustomRequestCultureProviderThatGetsCultureInfoFromUrl()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar")
}
};
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(context =>
{
var culture = GetCultureInfoFromUrl(context, options.SupportedCultures);
var requestCulture = new ProviderCultureResult(culture);
return Task.FromResult(requestCulture);
}));
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/ar/page");
}
}
private string GetCultureInfoFromUrl(HttpContext context, IList<CultureInfo> supportedCultures)
{
var currentCulture = "en";
var segments = context.Request.Path.Value.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length > 1 && segments[0].Length == 2)
{
currentCulture = segments[0];
}
return currentCulture;
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.Extensions.Logging.Testing" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,298 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.Extensions.Localization
{
public class QueryStringRequestCultureProviderTest
{
[Fact]
public async Task GetCultureInfoFromQueryString()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
Assert.Equal("ar-YE", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?culture=ar-SA&ui-culture=ar-YE");
}
}
[Fact]
public async Task GetDefaultCultureInfoIfCultureKeysAreMissing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("en-US", requestCulture.Culture.Name);
Assert.Equal("en-US", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page");
}
}
[Fact]
public async Task GetDefaultCultureInfoIfCultureIsInSupportedCultureList()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("en-US", requestCulture.Culture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?culture=ar-XY&ui-culture=ar-SA");
}
}
[Fact]
public async Task GetDefaultCultureInfoIfUICultureIsNotInSupportedList()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("en-US", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?culture=ar-SA&ui-culture=ar-XY");
}
}
[Fact]
public async Task GetSameCultureInfoIfCultureKeyIsMissing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
Assert.Equal("ar-SA", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?ui-culture=ar-SA");
}
}
[Fact]
public async Task GetSameCultureInfoIfUICultureKeyIsMissing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
}
});
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
Assert.Equal("ar-SA", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?culture=ar-SA");
}
}
[Fact]
public async Task GetCultureInfoFromQueryStringWithCustomKeys()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
var provider = new QueryStringRequestCultureProvider();
provider.QueryStringKey = "c";
provider.UIQueryStringKey = "uic";
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("ar-SA", requestCulture.Culture.Name);
Assert.Equal("ar-YE", requestCulture.UICulture.Name);
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?c=ar-SA&uic=ar-YE");
}
}
[Fact]
public async Task GetTheRightCultureInfoRegardlessOfCultureNameCasing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("FR")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("FR")
}
};
var provider = new QueryStringRequestCultureProvider();
provider.QueryStringKey = "c";
provider.UIQueryStringKey = "uic";
options.RequestCultureProviders.Insert(0, provider);
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
Assert.Equal("fr", requestCulture.Culture.ToString());
Assert.Equal("fr", requestCulture.UICulture.ToString());
return Task.FromResult(0);
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page?c=FR&uic=FR");
}
}
}
}

View File

@ -0,0 +1,136 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Xunit;
namespace Microsoft.AspNetCore.Localization
{
public class RequestLocalizationOptionsTest : IDisposable
{
private readonly CultureInfo _initialCulture;
private readonly CultureInfo _initialUICulture;
public RequestLocalizationOptionsTest()
{
_initialCulture = CultureInfo.CurrentCulture;
_initialUICulture = CultureInfo.CurrentUICulture;
}
[Fact]
public void DefaultRequestCulture_DefaultsToCurrentCulture()
{
// Arrange/Act
var options = new RequestLocalizationOptions();
// Assert
Assert.NotNull(options.DefaultRequestCulture);
Assert.Equal(CultureInfo.CurrentCulture, options.DefaultRequestCulture.Culture);
Assert.Equal(CultureInfo.CurrentUICulture, options.DefaultRequestCulture.UICulture);
}
[Fact]
public void DefaultRequestCulture_DefaultsToCurrentCultureWhenExplicitlySet()
{
// Arrange
var explicitCulture = new CultureInfo("fr-FR");
CultureInfo.CurrentCulture = explicitCulture;
CultureInfo.CurrentUICulture = explicitCulture;
// Act
var options = new RequestLocalizationOptions();
// Assert
Assert.Equal(explicitCulture, options.DefaultRequestCulture.Culture);
Assert.Equal(explicitCulture, options.DefaultRequestCulture.UICulture);
}
[Fact]
public void DefaultRequestCulture_ThrowsWhenTryingToSetToNull()
{
// Arrange
var options = new RequestLocalizationOptions();
// Act/Assert
Assert.Throws<ArgumentNullException>(() => options.DefaultRequestCulture = null);
}
[Fact]
public void SupportedCultures_DefaultsToCurrentCulture()
{
// Arrange/Act
var options = new RequestLocalizationOptions();
// Assert
Assert.Collection(options.SupportedCultures, item => Assert.Equal(CultureInfo.CurrentCulture, item));
Assert.Collection(options.SupportedUICultures, item => Assert.Equal(CultureInfo.CurrentUICulture, item));
}
[Fact]
public void SupportedCultures_DefaultsToCurrentCultureWhenExplicitlySet()
{
// Arrange
var explicitCulture = new CultureInfo("fr-FR");
CultureInfo.CurrentCulture = explicitCulture;
CultureInfo.CurrentUICulture = explicitCulture;
// Act
var options = new RequestLocalizationOptions();
// Assert
Assert.Collection(options.SupportedCultures, item => Assert.Equal(explicitCulture, item));
Assert.Collection(options.SupportedUICultures, item => Assert.Equal(explicitCulture, item));
}
[Fact]
public void BuilderAPIs_AddSupportedCultures()
{
// Arrange
var supportedCultures = new[] { "en-US", "ar-YE" };
// Act
var options = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures);
// Assert
Assert.Equal(supportedCultures, options.SupportedCultures.Select(c => c.Name));
}
[Fact]
public void BuilderAPIs_AddSupportedUICultures()
{
// Arrange
var supportedUICultures = new[] { "en-US", "ar-YE" };
// Act
var options = new RequestLocalizationOptions()
.AddSupportedUICultures(supportedUICultures);
// Assert
Assert.Equal(supportedUICultures, options.SupportedUICultures.Select(c => c.Name));
}
[Fact]
public void BuilderAPIs_SetDefaultCulture()
{
// Arrange
var defaultCulture = "ar-YE";
// Act
var options = new RequestLocalizationOptions()
.SetDefaultCulture(defaultCulture);
// Assert
Assert.Equal(defaultCulture, options.DefaultRequestCulture.Culture.Name);
}
public void Dispose()
{
CultureInfo.CurrentCulture = _initialCulture;
CultureInfo.CurrentUICulture = _initialUICulture;
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ResourcesClassLibraryNoAttribute\ResourcesClassLibraryNoAttribute.csproj" />
<ProjectReference Include="..\ResourcesClassLibraryWithAttribute\ResourcesClassLibraryWithAttribute.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Localization" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.AspNetCore.Testing" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
<Reference Include="Microsoft.Extensions.Localization" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace LocalizationWebsite.Models
{
public class Customer
{
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from Customer in Models folder</value>
</data>
</root>

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public static class Program
{
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.ConfigureLogging((_, factory) =>
{
factory.AddConsole();
factory.AddFilter("Console", level => level >= LogLevel.Warning);
})
.UseKestrel()
.UseConfiguration(config)
.UseStartup("LocalizationWebsite")
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from Customer in resources folder</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>We shouldn't get the english hello!</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from StartupResourcesInFolder</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from Test in resources folder</value>
</data>
</root>

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupBuilderAPIs
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizer<Customer> customerStringLocalizer)
{
var supportedCultures = new[] { "en-US", "fr-FR" };
app.UseRequestLocalization(options =>
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
.SetDefaultCulture("ar-YE")
);
app.Run(async (context) =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
await context.Response.WriteAsync(customerStringLocalizer["Hello"]);
});
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
namespace LocalizationWebsite
{
public class StartupCustomCulturePreserved
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization();
}
public void Configure(
IApplicationBuilder app)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>()
{
new CultureInfo("en-US") { NumberFormat= { CurrencySymbol = "kr" } }
},
SupportedUICultures = new List<CultureInfo>()
{
new CultureInfo("en-US") { NumberFormat= { CurrencySymbol = "kr" } }
}
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(10.ToString("C"));
});
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupGetAllStrings
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizer<Customer> customerStringLocalizer)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
},
SupportedUICultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
}
});
app.Run(async (context) =>
{
var strings = customerStringLocalizer.GetAllStrings();
await context.Response.WriteAsync(strings.Count().ToString());
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(string.Join(" ", strings));
});
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupResourcesAtRootFolder
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization();
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizerFactory stringLocalizerFactory,
IStringLocalizer<StartupResourcesAtRootFolder> startupStringLocalizer,
IStringLocalizer<Customer> customerStringLocalizer)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
},
SupportedUICultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
}
});
var location = typeof(LocalizationWebsite.StartupResourcesAtRootFolder).GetTypeInfo().Assembly.GetName().Name;
var stringLocalizer = stringLocalizerFactory.Create("Test", location: location);
app.Run(async (context) =>
{
await context.Response.WriteAsync(startupStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(stringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(customerStringLocalizer["Hello"]);
});
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from StartupResourcesAtRootFolder</value>
</data>
</root>

View File

@ -0,0 +1,68 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupResourcesInClassLibrary
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizerFactory stringLocalizerFactory)
{
var supportedCultures = new List<CultureInfo>()
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
var noAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryNoAttribute.Model));
var withAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryWithAttribute.Model));
var noAttributeAssembly = typeof(ResourcesClassLibraryNoAttribute.Model).GetTypeInfo().Assembly;
var noAttributeName = new AssemblyName(noAttributeAssembly.FullName).Name;
var noAttributeNameStringLocalizer = stringLocalizerFactory.Create(
nameof(ResourcesClassLibraryNoAttribute.Model),
noAttributeName);
var withAttributeAssembly = typeof(ResourcesClassLibraryWithAttribute.Model).GetTypeInfo().Assembly;
var withAttributeName = new AssemblyName(withAttributeAssembly.FullName).Name;
var withAttributeNameStringLocalizer = stringLocalizerFactory.Create(
nameof(ResourcesClassLibraryWithAttribute.Model),
withAttributeName);
app.Run(async (context) =>
{
await context.Response.WriteAsync(noAttributeNameStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(noAttributeStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(withAttributeNameStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(withAttributeStringLocalizer["Hello"]);
});
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupResourcesInFolder
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizerFactory stringLocalizerFactory,
IStringLocalizer<StartupResourcesInFolder> startupStringLocalizer,
IStringLocalizer<Customer> custromerStringLocalizer,
// This localizer is used in tests to prevent a regression of https://github.com/aspnet/Localization/issues/293
// Namely that english was always being returned if it existed.
IStringLocalizer<StartupCustomCulturePreserved> customCultureLocalizer)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
},
SupportedUICultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
}
});
var assemblyName = typeof(StartupResourcesInFolder).GetTypeInfo().Assembly.GetName().Name;
var stringLocalizer = stringLocalizerFactory.Create("Test", assemblyName);
app.Run(async (context) =>
{
await context.Response.WriteAsync(startupStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(stringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(custromerStringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(customCultureLocalizer["Hello"]);
});
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from Test in root folder</value>
</data>
</root>

View File

@ -0,0 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace ResourcesClassLibraryNoAttribute
{
public class Model
{
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from ResourcesClassLibraryNoAttribute</value>
</data>
</root>

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Microsoft.Extensions.Localization;
[assembly: ResourceLocation("ResourceFolder")]
[assembly: RootNamespace("Alternate.Namespace")]

View File

@ -0,0 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace ResourcesClassLibraryWithAttribute
{
public class Model
{
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Hello" xml:space="preserve">
<value>Bonjour from ResourcesClassLibraryWithAttribute</value>
</data>
</root>

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Alternate.Namespace</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.Localization" />
</ItemGroup>
</Project>