diff --git a/src/ResGen/Program.cs b/src/ResGen/Program.cs
new file mode 100644
index 0000000000..fe7524c8de
--- /dev/null
+++ b/src/ResGen/Program.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Xml.Linq;
+
+namespace ConsoleApplication
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ // we are assuming resgen is run with 'dotnet run'
+ // so we can use relative paths
+ foreach (string folder in Directory.EnumerateDirectories(".."))
+ {
+ string moduleName = Path.GetFileName(folder);
+ string resourcePath = Path.Combine(folder, "resources");
+ if (Directory.Exists(resourcePath))
+ {
+ string genFolder = Path.Combine(folder, "gen");
+ if (!Directory.Exists(genFolder))
+ {
+ Directory.CreateDirectory(genFolder);
+ }
+
+ foreach (string resxPath in Directory.EnumerateFiles(resourcePath, "*.resx"))
+ {
+ string className = Path.GetFileNameWithoutExtension(resxPath);
+ string sourceCode = GetStronglyTypeCsFileForResx(resxPath, moduleName, className);
+ string outPath = Path.Combine(genFolder, className + ".cs");
+ Console.WriteLine("ResGen for " + outPath);
+ File.WriteAllText(outPath, sourceCode);
+ }
+ }
+ }
+ }
+
+ private static string GetStronglyTypeCsFileForResx(string xmlPath, string moduleName, string className)
+ {
+ // Example
+ //
+ // className = Full.Name.Of.The.ClassFoo
+ // shortClassName = ClassFoo
+ // namespaceName = Full.Name.Of.The
+
+ string shortClassName = className;
+ string namespaceName = null;
+ int lastIndexOfDot = className.LastIndexOf('.');
+ if (lastIndexOfDot != -1)
+ {
+ namespaceName = className.Substring(0, lastIndexOfDot);
+ shortClassName = className.Substring(lastIndexOfDot + 1);
+ }
+
+ var entries = new StringBuilder();
+ XElement root = XElement.Parse(File.ReadAllText(xmlPath));
+ foreach (var data in root.Elements("data"))
+ {
+ string value = data.Value.Replace("\n", "\n ///");
+ string name = data.Attribute("name").Value.Replace(' ', '_');
+ entries.AppendFormat(ENTRY, name, value);
+ }
+
+ string bodyCode = string.Format(BODY, shortClassName, moduleName, entries.ToString(), className);
+ if (namespaceName != null)
+ {
+ bodyCode = string.Format(NAMESPACE, namespaceName, bodyCode);
+ }
+
+ string resultCode = string.Format(BANNER, bodyCode).Replace("\r\n?|\n", "\r\n");
+ return resultCode;
+ }
+
+ private static readonly string BANNER = @"
+//------------------------------------------------------------------------------
+//
+// This code was generated by a dotnet run from src\ResGen folder.
+// To add or remove a member, edit your .resx file then rerun src\ResGen.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+{0}
+";
+
+ private static readonly string NAMESPACE = @"
+namespace {0} {{
+{1}
+}}
+";
+ private static readonly string BODY = @"
+using System;
+using System.Reflection;
+
+///
+/// A strongly-typed resource class, for looking up localized strings, etc.
+///
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")]
+[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+
+internal class {0} {{
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(""Microsoft.Performance"", ""CA1811:AvoidUncalledPrivateCode"")]
+ internal {0}() {{
+ }}
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {{
+ get {{
+ if (object.ReferenceEquals(resourceMan, null)) {{
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(""{1}.resources.{3}"", typeof({0}).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }}
+ return resourceMan;
+ }}
+ }}
+
+ ///
+ /// Overrides the current threads CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {{
+ get {{
+ return resourceCulture;
+ }}
+ set {{
+ resourceCulture = value;
+ }}
+ }}
+ {2}
+}}
+";
+
+ private static readonly string ENTRY = @"
+
+ ///
+ /// Looks up a localized string similar to {1}
+ ///
+ internal static string {0} {{
+ get {{
+ return ResourceManager.GetString(""{0}"", resourceCulture);
+ }}
+ }}
+";
+
+ }
+}
diff --git a/src/ResGen/project.json b/src/ResGen/project.json
new file mode 100644
index 0000000000..0dea62d8e4
--- /dev/null
+++ b/src/ResGen/project.json
@@ -0,0 +1,28 @@
+{
+ "name": "resgen",
+ "version": "1.0.0-*",
+ "description": "Generates C# typed bindings for .resx files",
+
+ "buildOptions": {
+ "emitEntryPoint": true
+ },
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.NETCore.App": "1.0.0",
+ "System.Xml.XDocument": "4.0.11"
+ }
+ }
+ },
+
+ "runtimes": {
+ "ubuntu.14.04-x64": { },
+ "debian.8-x64": { },
+ "centos.7-x64": { },
+ "win7-x64": { },
+ "win81-x64": { },
+ "win10-x64": { },
+ "osx.10.11-x64": { }
+ }
+}