mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
- Added NadekoBot.Generators projects which will contain source generators
- Implemented initial version of the response strings source generator - Creates a class with property names equivalent to key names in responses.en-US.json - Each Property has struct type (with generic type parameters matching the number of string format placeholders) for type safe GetText implementation - Struct types are readonly refs as they should be ephermal, and only used to pass string keys to GetText
This commit is contained in:
@@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Tests", "src\Nade
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "src\NadekoBot.Coordinator\NadekoBot.Coordinator.csproj", "{AE9B7F8C-81D7-4401-83A3-643B38258374}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "src\NadekoBot.Coordinator\NadekoBot.Coordinator.csproj", "{AE9B7F8C-81D7-4401-83A3-643B38258374}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -54,6 +56,12 @@ Global
|
|||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
{AE9B7F8C-81D7-4401-83A3-643B38258374}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AE9B7F8C-81D7-4401-83A3-643B38258374}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -64,6 +72,7 @@ Global
|
|||||||
{2F4CF6D6-0C2F-4944-B204-9508CDA53195} = {6058FEDF-A318-4CD4-8F04-A7E8E7EC8874}
|
{2F4CF6D6-0C2F-4944-B204-9508CDA53195} = {6058FEDF-A318-4CD4-8F04-A7E8E7EC8874}
|
||||||
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
||||||
|
159
src/NadekoBot.Generators/LocalizedStringsGenerator.cs
Normal file
159
src/NadekoBot.Generators/LocalizedStringsGenerator.cs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
using System;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NadekoBot.Generators
|
||||||
|
{
|
||||||
|
internal class FieldData
|
||||||
|
{
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class LocalizedStringsGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
private const string LocStrSource = @"namespace NadekoBot
|
||||||
|
{
|
||||||
|
public readonly ref struct LocStr
|
||||||
|
{
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public LocStr(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LocStr(string data)
|
||||||
|
=> new LocStr(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ref struct LocStr<T1>
|
||||||
|
{
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public LocStr(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LocStr<T1>(string data)
|
||||||
|
=> new LocStr<T1>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ref struct LocStr<T1, T2>
|
||||||
|
{
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public LocStr(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LocStr<T1, T2>(string data)
|
||||||
|
=> new LocStr<T1, T2>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ref struct LocStr<T1, T2, T3>
|
||||||
|
{
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public LocStr(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LocStr<T1, T2, T3>(string data)
|
||||||
|
=> new LocStr<T1, T2, T3>(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("namespace NadekoBot");
|
||||||
|
sw.WriteLine("{");
|
||||||
|
sw.Indent++;
|
||||||
|
|
||||||
|
sw.WriteLine("public static class Strs");
|
||||||
|
sw.WriteLine("{");
|
||||||
|
sw.Indent++;
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
sw.WriteLine($"public static {field.Type} {field.Name} => \"{field.Name}\";");
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Indent--;
|
||||||
|
sw.WriteLine("}");
|
||||||
|
sw.Indent--;
|
||||||
|
sw.WriteLine("}");
|
||||||
|
|
||||||
|
|
||||||
|
sw.Flush();
|
||||||
|
context.AddSource("Strs.cs", stringWriter.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.AddSource("LocStr.cs", LocStrSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FieldData> GetFields(string dataText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(dataText))
|
||||||
|
throw new ArgumentNullException(nameof(dataText));
|
||||||
|
|
||||||
|
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(dataText);
|
||||||
|
|
||||||
|
var list = new List<FieldData>();
|
||||||
|
foreach (var entry in data)
|
||||||
|
{
|
||||||
|
list.Add(new FieldData()
|
||||||
|
{
|
||||||
|
Type = GetFieldType(entry.Value),
|
||||||
|
Name = entry.Key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFieldType(string value)
|
||||||
|
{
|
||||||
|
var matches = Regex.Matches(value, @"{(?<num>\d)}");
|
||||||
|
int max = 0;
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
max = Math.Max(max, int.Parse(match.Groups["num"].Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max == 0)
|
||||||
|
return "LocStr";
|
||||||
|
if (max == 1)
|
||||||
|
return "LocStr<string>";
|
||||||
|
if (max == 2)
|
||||||
|
return "LocStr<string, string>";
|
||||||
|
if (max == 3)
|
||||||
|
return "LocStr<string, string, string>";
|
||||||
|
|
||||||
|
return "!Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/NadekoBot.Generators/NadekoBot.Generators.csproj
Normal file
23
src/NadekoBot.Generators/NadekoBot.Generators.csproj
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" PrivateAssets="all" GeneratePathProperty="true" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="GetDependencyTargetPaths">
|
||||||
|
<ItemGroup>
|
||||||
|
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
@@ -31,6 +31,9 @@ namespace NadekoBot.Modules
|
|||||||
|
|
||||||
protected string GetText(string key) =>
|
protected string GetText(string key) =>
|
||||||
Strings.GetText(key, _cultureInfo);
|
Strings.GetText(key, _cultureInfo);
|
||||||
|
|
||||||
|
protected string GetText(in LocStr key) =>
|
||||||
|
Strings.GetText(key.Key, _cultureInfo);
|
||||||
|
|
||||||
protected string GetText(string key, params object[] args) =>
|
protected string GetText(string key, params object[] args) =>
|
||||||
Strings.GetText(key, _cultureInfo, args);
|
Strings.GetText(key, _cultureInfo, args);
|
||||||
|
@@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||||
.WithDescription(ctx.User.ToString())
|
.WithDescription(ctx.User.ToString())
|
||||||
.AddField("❓ " + GetText("question"), question, false)
|
.AddField("❓ " + GetText("question"), question, false)
|
||||||
.AddField("🎱 " + GetText("8ball"), res, false));
|
.AddField("🎱 " + GetText("_8ball"), res, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
|
@@ -532,7 +532,7 @@ namespace NadekoBot.Modules.Searches
|
|||||||
var embed = _eb.Create()
|
var embed = _eb.Create()
|
||||||
.WithDescription(ctx.User.Mention)
|
.WithDescription(ctx.User.Mention)
|
||||||
.AddField(GetText("word"), data.Word, true)
|
.AddField(GetText("word"), data.Word, true)
|
||||||
.AddField(GetText("class"), data.WordType, true)
|
.AddField(GetText("_class"), data.WordType, true)
|
||||||
.AddField(GetText("definition"), data.Definition)
|
.AddField(GetText("definition"), data.Definition)
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
|
@@ -190,13 +190,13 @@ namespace NadekoBot.Modules.Utility
|
|||||||
string description = "";
|
string description = "";
|
||||||
if (_service.IsNoRedundant(runner.Repeater.Id))
|
if (_service.IsNoRedundant(runner.Repeater.Id))
|
||||||
{
|
{
|
||||||
description = Format.Underline(Format.Bold(GetText("no_redundant:"))) + "\n\n";
|
description = Format.Underline(Format.Bold(GetText("no_redundant"))) + "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
description += $"<#{runner.Repeater.ChannelId}>\n" +
|
description += $"<#{runner.Repeater.ChannelId}>\n" +
|
||||||
$"`{GetText("interval:")}` {intervalString}\n" +
|
$"`{GetText("Comment")}` {intervalString}\n" +
|
||||||
$"`{GetText("executes_in:")}` {executesInString}\n" +
|
$"`{GetText("executes_in_colon")}` {executesInString}\n" +
|
||||||
$"`{GetText("message:")}` {message}";
|
$"`{GetText("message_colon")}` {message}";
|
||||||
|
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
@@ -249,7 +249,7 @@ namespace NadekoBot.Modules.Utility
|
|||||||
.WithAuthor($"NadekoBot v{StatsService.BotVersion}",
|
.WithAuthor($"NadekoBot v{StatsService.BotVersion}",
|
||||||
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
|
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
|
||||||
"https://nadekobot.readthedocs.io/en/latest/")
|
"https://nadekobot.readthedocs.io/en/latest/")
|
||||||
.AddField(GetText("author"), _stats.Author, true)
|
.AddField(GetText(Strs.author), _stats.Author, true)
|
||||||
.AddField(GetText("botid"), _client.CurrentUser.Id.ToString(), true)
|
.AddField(GetText("botid"), _client.CurrentUser.Id.ToString(), true)
|
||||||
.AddField(GetText("shard"), $"#{_client.ShardId} / {_creds.TotalShards}", true)
|
.AddField(GetText("shard"), $"#{_client.ShardId} / {_creds.TotalShards}", true)
|
||||||
.AddField(GetText("commands_ran"), _stats.CommandsRan.ToString(), true)
|
.AddField(GetText("commands_ran"), _stats.CommandsRan.ToString(), true)
|
||||||
|
@@ -58,8 +58,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
||||||
|
<ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="data\strings\responses\responses.en-US.json" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="..\NadekoBot.Coordinator\Protos\coordinator.proto" GrpcServices="Client">
|
<Protobuf Include="..\NadekoBot.Coordinator\Protos\coordinator.proto" GrpcServices="Client">
|
||||||
<Link>Protos\coordinator.proto</Link>
|
<Link>Protos\coordinator.proto</Link>
|
||||||
|
@@ -323,7 +323,7 @@
|
|||||||
"waifu_reset_fail": "Failed resetting waifu stats. Make sure you have enough currency.",
|
"waifu_reset_fail": "Failed resetting waifu stats. Make sure you have enough currency.",
|
||||||
"waifu_reset_confirm": "This will reset your waifu stats",
|
"waifu_reset_confirm": "This will reset your waifu stats",
|
||||||
"waifu_reset_price": "Price: {0}",
|
"waifu_reset_price": "Price: {0}",
|
||||||
"8ball": "8ball",
|
"_8ball": "8ball",
|
||||||
"acrophobia": "Acrophobia",
|
"acrophobia": "Acrophobia",
|
||||||
"acro_ended_no_sub": "Game ended with no submissions.",
|
"acro_ended_no_sub": "Game ended with no submissions.",
|
||||||
"acro_no_votes_cast": "No votes cast. Game ended with no winner.",
|
"acro_no_votes_cast": "No votes cast. Game ended with no winner.",
|
||||||
@@ -508,7 +508,7 @@
|
|||||||
"cost": "Cost",
|
"cost": "Cost",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"word": "Word",
|
"word": "Word",
|
||||||
"class": "Class",
|
"_class": "Class",
|
||||||
"definition": "Definition",
|
"definition": "Definition",
|
||||||
"example": "Example",
|
"example": "Example",
|
||||||
"dropped": "Dropped",
|
"dropped": "Dropped",
|
||||||
@@ -629,10 +629,10 @@
|
|||||||
"repeater_removed": "Repeater #{0} Removed",
|
"repeater_removed": "Repeater #{0} Removed",
|
||||||
"repeater_exceed_limit": "You cannot have more than {0} repeaters per server.",
|
"repeater_exceed_limit": "You cannot have more than {0} repeaters per server.",
|
||||||
"repeater_remove_fail": "Failed removing repeater on that index. Either you've specified invalid index, or repeater was in executing state at that time, in which case, try again in a few seconds.",
|
"repeater_remove_fail": "Failed removing repeater on that index. Either you've specified invalid index, or repeater was in executing state at that time, in which case, try again in a few seconds.",
|
||||||
"interval:": "Interval:",
|
"interval_colon": "Interval:",
|
||||||
"executes_in:": "Executes in:",
|
"executes_in_colon": "Executes in:",
|
||||||
"message:": "Message:",
|
"message_colon": "Message:",
|
||||||
"no_redundant:": "Won't post duplicate message.",
|
"no_redundant": "Won't post duplicate message.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"nobody_playing_game": "Nobody is playing that game.",
|
"nobody_playing_game": "Nobody is playing that game.",
|
||||||
|
Reference in New Issue
Block a user