Culture names are now limited to a known list:

- Added tool to generate a set of known culture names from the OS/Fx
- CultureInfoCache is now limited to only caching/returning cultures from the known list
- #6
This commit is contained in:
damianedwards 2015-05-11 12:58:18 -07:00
parent ca4b85e19f
commit 9834a27728
7 changed files with 606 additions and 19 deletions

View File

@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CultureInfoGenerator", "src\CultureInfoGenerator\CultureInfoGenerator.xproj", "{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -42,6 +44,10 @@ Global
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Release|Any CPU.Build.0 = Release|Any CPU
{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -51,5 +57,6 @@ Global
{23E3BC23-3464-4D9B-BF78-02CB2182BEF0} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
{A1FCF259-70F6-4605-AA2D-E4B356BE771A} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
{55D9501F-15B9-4339-A0AB-6082850E5FCE} = {79878809-8D1C-4BD4-BA99-F1F13FF96FD8}
{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
EndGlobalSection
EndGlobal

View File

@ -123,7 +123,8 @@ $@"<!doctype html>
#if DNX451
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("zh-CHT").Name}\">{new CultureInfo("zh-CHT").DisplayName}</option>");
#endif
await context.Response.WriteAsync($" <option value=\"en-NOTREAL\">English (Not a real culture)</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>");
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>bd22ae1c-6631-4da6-874d-0dc0f803ceab</ProjectGuid>
<RootNamespace>CultureInfoGenerator</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,116 @@
// 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.IO;
using Microsoft.Framework.Runtime;
using Microsoft.Win32;
namespace CultureInfoGenerator
{
public class Program
{
private readonly string _appName;
private readonly string _appPath;
public Program(IApplicationEnvironment appEnvironment)
{
_appName = appEnvironment.ApplicationName;
_appPath = appEnvironment.ApplicationBasePath;
}
public void Main(string[] args)
{
var outputFilePath = args.Length > 0 ? args[0] : Path.Combine(_appPath, "../Microsoft.AspNet.Localization/Internal/CultureInfoList.cs");
var netFxVersion = Get45or451FromRegistry();
var windowsVersion = Environment.OSVersion;
using (var writer = new StreamWriter(outputFilePath, false))
{
writer.WriteLine($@"// 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;
namespace Microsoft.AspNet.Localization.Internal
{{
public static class CultureInfoList
{{
// This list of known cultures was generated by {_appName} using .NET Framework {netFxVersion} on
// {windowsVersion}.
// As new versions of .NET Framework and Windows are released, this list should be regenerated to ensure it
// contains the latest culture names.
public static readonly HashSet<string> KnownCultureNames = new HashSet<string>
{{"
);
var cultures = CultureInfo.GetCultures(
CultureTypes.NeutralCultures
| CultureTypes.InstalledWin32Cultures
| CultureTypes.SpecificCultures);
var format = " \"{0}\"";
for (int i = 0; i < cultures.Length; i++)
{
var culture = cultures[i];
writer.Write(format, culture.Name);
if (i < cultures.Length - 1)
{
writer.WriteLine(",");
}
else
{
// Last entry
writer.WriteLine();
}
}
writer.WriteLine(
@" };
}
}");
}
}
// .NET Framework detection code copied from https://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_d
private static string Get45or451FromRegistry()
{
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\"))
{
var releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
return CheckFor45DotVersion(releaseKey);
}
}
// Checking the version using >= will enable forward compatibility,
// however you should always compile your code on newer versions of
// the framework to ensure your app works the same.
private static string CheckFor45DotVersion(int releaseKey)
{
if (releaseKey >= 393273)
{
return "4.6 RC or later";
}
if ((releaseKey >= 379893))
{
return "4.5.2 or later";
}
if ((releaseKey >= 378675))
{
return "4.5.1 or later";
}
if ((releaseKey >= 378389))
{
return "4.5 or later";
}
// This line should never execute. A non-null release key should mean
// that 4.5 or later is installed.
return "No 4.5 or later version detected";
}
}
}

View File

@ -0,0 +1,16 @@
{
"version": "1.0.0-*",
"description": "Generates a list of known culture names from the OS using CultureInfo.GetCultures. This tool is intended to be run on Windows using full .NET Framework.",
"dependencies": {
"Microsoft.Framework.Runtime.Abstractions": "1.0.0-beta5-11739"
},
"commands": {
"CultureInfoGenerator": "CultureInfoGenerator"
},
"frameworks": {
"dnx451": { }
}
}

View File

@ -11,11 +11,11 @@ namespace Microsoft.AspNet.Localization.Internal
{
private static readonly ConcurrentDictionary<string, CacheEntry> _cache = new ConcurrentDictionary<string, CacheEntry>();
public static CultureInfo GetCultureInfo(string name, bool throwIfNotFound = false)
public static CultureInfo GetCultureInfo(string name)
{
// Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
// the CultureInfo ctor
if (name == null)
// 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 || !CultureInfoList.KnownCultureNames.Contains(name))
{
return null;
}
@ -26,17 +26,15 @@ namespace Microsoft.AspNet.Localization.Internal
{
return new CacheEntry(CultureInfo.ReadOnly(new CultureInfo(n)));
}
catch (CultureNotFoundException ex)
catch (CultureNotFoundException)
{
return new CacheEntry(ex);
// This can still throw as the list of culture names we have is generated from latest .NET Framework
// on latest Windows and thus contains names that won't be supported on lower framework or OS versions.
// We can just cache the null result in these cases as it's ultimately bound by the list anyway.
return new CacheEntry(cultureInfo: null);
}
});
if (entry.Exception != null && throwIfNotFound)
{
throw entry.Exception;
}
return entry.CultureInfo;
}
@ -47,14 +45,7 @@ namespace Microsoft.AspNet.Localization.Internal
CultureInfo = cultureInfo;
}
public CacheEntry(Exception exception)
{
Exception = exception;
}
public CultureInfo CultureInfo { get; }
public Exception Exception { get; }
}
}
}

View File

@ -0,0 +1,436 @@
// 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;
namespace Microsoft.AspNet.Localization.Internal
{
public static class CultureInfoList
{
// This list of known cultures was generated by CultureInfoGenerator using .NET Framework 4.6 RC or later on
// Microsoft Windows NT 6.2.9200.0.
// As new versions of .NET Framework and Windows are released, this list should be regenerated to ensure it
// contains the latest culture names.
public static readonly HashSet<string> KnownCultureNames = new HashSet<string>
{
"",
"af",
"af-ZA",
"am",
"am-ET",
"ar",
"ar-AE",
"ar-BH",
"ar-DZ",
"ar-EG",
"ar-IQ",
"ar-JO",
"ar-KW",
"ar-LB",
"ar-LY",
"ar-MA",
"ar-OM",
"ar-QA",
"ar-SA",
"ar-SY",
"ar-TN",
"ar-YE",
"arn",
"arn-CL",
"as",
"as-IN",
"az",
"az-Cyrl",
"az-Cyrl-AZ",
"az-Latn",
"az-Latn-AZ",
"ba",
"ba-RU",
"be",
"be-BY",
"bg",
"bg-BG",
"bn",
"bn-BD",
"bn-IN",
"bo",
"bo-CN",
"br",
"br-FR",
"bs",
"bs-Cyrl",
"bs-Cyrl-BA",
"bs-Latn",
"bs-Latn-BA",
"ca",
"ca-ES",
"ca-ES-valencia",
"chr",
"chr-Cher",
"chr-Cher-US",
"co",
"co-FR",
"cs",
"cs-CZ",
"cy",
"cy-GB",
"da",
"da-DK",
"de",
"de-AT",
"de-CH",
"de-DE",
"de-LI",
"de-LU",
"dsb",
"dsb-DE",
"dv",
"dv-MV",
"el",
"el-GR",
"en",
"en-029",
"en-AU",
"en-BZ",
"en-CA",
"en-GB",
"en-HK",
"en-IE",
"en-IN",
"en-JM",
"en-MY",
"en-NZ",
"en-PH",
"en-SG",
"en-TT",
"en-US",
"en-ZA",
"en-ZW",
"es",
"es-419",
"es-AR",
"es-BO",
"es-CL",
"es-CO",
"es-CR",
"es-DO",
"es-EC",
"es-ES",
"es-GT",
"es-HN",
"es-MX",
"es-NI",
"es-PA",
"es-PE",
"es-PR",
"es-PY",
"es-SV",
"es-US",
"es-UY",
"es-VE",
"et",
"et-EE",
"eu",
"eu-ES",
"fa",
"fa-IR",
"ff",
"ff-Latn",
"ff-Latn-SN",
"fi",
"fi-FI",
"fil",
"fil-PH",
"fo",
"fo-FO",
"fr",
"fr-BE",
"fr-CA",
"fr-CD",
"fr-CH",
"fr-CI",
"fr-CM",
"fr-FR",
"fr-HT",
"fr-LU",
"fr-MA",
"fr-MC",
"fr-ML",
"fr-RE",
"fr-SN",
"fy",
"fy-NL",
"ga",
"ga-IE",
"gd",
"gd-GB",
"gl",
"gl-ES",
"gn",
"gn-PY",
"gsw",
"gsw-FR",
"gu",
"gu-IN",
"ha",
"ha-Latn",
"ha-Latn-NG",
"haw",
"haw-US",
"he",
"he-IL",
"hi",
"hi-IN",
"hr",
"hr-BA",
"hr-HR",
"hsb",
"hsb-DE",
"hu",
"hu-HU",
"hy",
"hy-AM",
"id",
"id-ID",
"ig",
"ig-NG",
"ii",
"ii-CN",
"is",
"is-IS",
"it",
"it-CH",
"it-IT",
"iu",
"iu-Cans",
"iu-Cans-CA",
"iu-Latn",
"iu-Latn-CA",
"ja",
"ja-JP",
"jv",
"jv-Latn",
"jv-Latn-ID",
"ka",
"ka-GE",
"kk",
"kk-KZ",
"kl",
"kl-GL",
"km",
"km-KH",
"kn",
"kn-IN",
"ko",
"ko-KR",
"kok",
"kok-IN",
"ku",
"ku-Arab",
"ku-Arab-IQ",
"ky",
"ky-KG",
"lb",
"lb-LU",
"lo",
"lo-LA",
"lt",
"lt-LT",
"lv",
"lv-LV",
"mg",
"mg-MG",
"mi",
"mi-NZ",
"mk",
"mk-MK",
"ml",
"ml-IN",
"mn",
"mn-Cyrl",
"mn-MN",
"mn-Mong",
"mn-Mong-CN",
"mn-Mong-MN",
"moh",
"moh-CA",
"mr",
"mr-IN",
"ms",
"ms-BN",
"ms-MY",
"mt",
"mt-MT",
"my",
"my-MM",
"nb",
"nb-NO",
"ne",
"ne-IN",
"ne-NP",
"nl",
"nl-BE",
"nl-NL",
"nn",
"nn-NO",
"no",
"nqo",
"nqo-GN",
"nso",
"nso-ZA",
"oc",
"oc-FR",
"om",
"om-ET",
"or",
"or-IN",
"pa",
"pa-Arab",
"pa-Arab-PK",
"pa-IN",
"pl",
"pl-PL",
"prs",
"prs-AF",
"ps",
"ps-AF",
"pt",
"pt-AO",
"pt-BR",
"pt-PT",
"qut",
"qut-GT",
"quz",
"quz-BO",
"quz-EC",
"quz-PE",
"rm",
"rm-CH",
"ro",
"ro-MD",
"ro-RO",
"ru",
"ru-RU",
"rw",
"rw-RW",
"sa",
"sa-IN",
"sah",
"sah-RU",
"sd",
"sd-Arab",
"sd-Arab-PK",
"se",
"se-FI",
"se-NO",
"se-SE",
"si",
"si-LK",
"sk",
"sk-SK",
"sl",
"sl-SI",
"sma",
"sma-NO",
"sma-SE",
"smj",
"smj-NO",
"smj-SE",
"smn",
"smn-FI",
"sms",
"sms-FI",
"sn",
"sn-Latn",
"sn-Latn-ZW",
"so",
"so-SO",
"sq",
"sq-AL",
"sr",
"sr-Cyrl",
"sr-Cyrl-BA",
"sr-Cyrl-CS",
"sr-Cyrl-ME",
"sr-Cyrl-RS",
"sr-Latn",
"sr-Latn-BA",
"sr-Latn-CS",
"sr-Latn-ME",
"sr-Latn-RS",
"st",
"st-ZA",
"sv",
"sv-FI",
"sv-SE",
"sw",
"sw-KE",
"syr",
"syr-SY",
"ta",
"ta-IN",
"ta-LK",
"te",
"te-IN",
"tg",
"tg-Cyrl",
"tg-Cyrl-TJ",
"th",
"th-TH",
"ti",
"ti-ER",
"ti-ET",
"tk",
"tk-TM",
"tn",
"tn-BW",
"tn-ZA",
"tr",
"tr-TR",
"ts",
"ts-ZA",
"tt",
"tt-RU",
"tzm",
"tzm-Latn",
"tzm-Latn-DZ",
"tzm-Tfng",
"tzm-Tfng-MA",
"ug",
"ug-CN",
"uk",
"uk-UA",
"ur",
"ur-IN",
"ur-PK",
"uz",
"uz-Cyrl",
"uz-Cyrl-UZ",
"uz-Latn",
"uz-Latn-UZ",
"vi",
"vi-VN",
"wo",
"wo-SN",
"xh",
"xh-ZA",
"yo",
"yo-NG",
"zgh",
"zgh-Tfng",
"zgh-Tfng-MA",
"zh",
"zh-CN",
"zh-Hans",
"zh-Hant",
"zh-HK",
"zh-MO",
"zh-SG",
"zh-TW",
"zu",
"zu-ZA",
"zh-CHS",
"zh-CHT"
};
}
}