diff --git a/Ellie.sln b/Ellie.sln
index 5c73b79..88d2626 100644
--- a/Ellie.sln
+++ b/Ellie.sln
@@ -5,6 +5,14 @@ VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B2F1537-4BF0-422B-A0DD-8F9CCEFB340F}"
+ProjectSection(SolutionItems) = preProject
+ CHANGELOG.md = CHANGELOG.md
+ LICENSE.md = LICENSE.md
+ README.md = README.md
+ Dockerfile = Dockerfile
+ NuGet.Config = NuGet.Config
+EndProjectSection
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie", "src\Ellie\Ellie.csproj", "{2BAF005E-781D-45FF-B218-E6361F5E8CD4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ayu", "ayu", "{5284415D-A43F-4539-9483-410124199743}"
diff --git a/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj b/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj
new file mode 100644
index 0000000..a1d67da
--- /dev/null
+++ b/src/Ellie.Bot.Generators.Strings/Ellie.Bot.Generators.Strings.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netstandard2.0
+ latest
+ false
+ true
+ true
+ enable
+ enable
+ Ellie.Generators
+
+
+
+
+
+
+
+
+
+ $(GetTargetPathDependsOn);GetDependencyTargetPaths
+
+
+
+
+
+
+
+
+
diff --git a/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs b/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs
new file mode 100644
index 0000000..0bf2875
--- /dev/null
+++ b/src/Ellie.Bot.Generators.Strings/LocalizedStringsGenerator.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System.CodeDom.Compiler;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using Microsoft.CodeAnalysis;
+using Newtonsoft.Json;
+
+namespace Ellie.Generators
+{
+ internal readonly struct TranslationPair
+ {
+ public string Name { get; }
+ public string Value { get; }
+
+ public TranslationPair(string name, string value)
+ {
+ Name = name;
+ Value = value;
+ }
+ }
+
+ [Generator]
+ public class LocalizedStringsGenerator : ISourceGenerator
+ {
+ // private const string LOC_STR_SOURCE = @"namespace Ellie
+ // {
+ // public readonly struct LocStr
+ // {
+ // public readonly string Key;
+ // public readonly object[] Params;
+ //
+ // public LocStr(string key, params object[] data)
+ // {
+ // Key = key;
+ // Params = data;
+ // }
+ // }
+ // }";
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var file = context.AdditionalFiles.First(x => x.Path.EndsWith("responses.en-US.json"));
+
+ var fields = GetFields(file.GetText()?.ToString());
+
+ using (var stringWriter = new StringWriter())
+ using (var sw = new IndentedTextWriter(stringWriter))
+ {
+ sw.WriteLine("#pragma warning disable CS8981");
+ sw.WriteLine("namespace Ellie;");
+ sw.WriteLine();
+
+ sw.WriteLine("public static class strs");
+ sw.WriteLine("{");
+ sw.Indent++;
+
+ var typedParamStrings = new List(10);
+ foreach (var field in fields)
+ {
+ var matches = Regex.Matches(field.Value, @"{(?\d)[}:]");
+ var max = 0;
+ foreach (Match match in matches)
+ {
+ max = Math.Max(max, int.Parse(match.Groups["num"].Value) + 1);
+ }
+
+ typedParamStrings.Clear();
+ var typeParams = new string[max];
+ var passedParamString = string.Empty;
+ for (var i = 0; i < max; i++)
+ {
+ typedParamStrings.Add($"in T{i} p{i}");
+ passedParamString += $", p{i}";
+ typeParams[i] = $"T{i}";
+ }
+
+ var sig = string.Empty;
+ var typeParamStr = string.Empty;
+ if (max > 0)
+ {
+ sig = $"({string.Join(", ", typedParamStrings)})";
+ typeParamStr = $"<{string.Join(", ", typeParams)}>";
+ }
+
+ sw.WriteLine("public static LocStr {0}{1}{2} => new LocStr(\"{3}\"{4});",
+ field.Name,
+ typeParamStr,
+ sig,
+ field.Name,
+ passedParamString);
+ }
+
+ sw.Indent--;
+ sw.WriteLine("}");
+
+
+ sw.Flush();
+ context.AddSource("strs.g.cs", stringWriter.ToString());
+ }
+
+ // context.AddSource("LocStr.g.cs", LOC_STR_SOURCE);
+ }
+
+ private List GetFields(string? dataText)
+ {
+ if (string.IsNullOrWhiteSpace(dataText))
+ return new();
+
+ Dictionary data;
+ try
+ {
+ var output = JsonConvert.DeserializeObject>(dataText!);
+ if (output is null)
+ return new();
+
+ data = output;
+ }
+ catch
+ {
+ Debug.WriteLine("Failed parsing responses file.");
+ return new();
+ }
+
+ var list = new List();
+ foreach (var entry in data)
+ {
+ list.Add(new(
+ entry.Key,
+ entry.Value
+ ));
+ }
+
+ return list;
+ }
+ }
+}
diff --git a/src/Ellie.Bot.Generators.Strings/README.md b/src/Ellie.Bot.Generators.Strings/README.md
new file mode 100644
index 0000000..b17dd47
--- /dev/null
+++ b/src/Ellie.Bot.Generators.Strings/README.md
@@ -0,0 +1,24 @@
+## Generators
+
+Project which contains source generators required for Ellie project
+
+---
+### 1) Localized Strings Generator
+
+ -- Why --
+ Type safe response strings access, and enforces correct usage of response strings.
+
+ -- How it works --
+ Creates a file "strs.cs" containing a class called "strs" in "Ellie" namespace.
+
+ Loads "data/strings/responses.en-US.json" and creates a property or a function for each key in the responses json file based on whether the value has string format placeholders or not.
+
+ - If a value has no placeholders, it creates a property in the strs class which returns an instance of a LocStr struct containing only the key and no replacement parameters
+
+ - If a value has placeholders, it creates a function with the same number of arguments as the number of placeholders, and passes those arguments to the LocStr instance
+
+ -- How to use --
+ 1. Add a new key to responses.en-US.json "greet_me": "Hello, {0}"
+ 2. You now have access to a function strs.greet_me(obj p1)
+ 3. Using "GetText(strs.greet_me("Me"))" will return "Hello, Me"
+