Skip to content

Commit

Permalink
[NEW-FEATURE] Create tool for generating resx file from localized str…
Browse files Browse the repository at this point in the history
…ings from blazor components (#210)

* Create draft PR for #209

* added ltr tool, wip on doc

* added simple documentation and fix tests

* add ltr to pipeline

* adds required target framework for lrt

---------

Co-authored-by: Specter-13 <Specter-13@users.noreply.github.com>
Co-authored-by: Specter-13 <56168909+Specter-13@users.noreply.github.com>
Co-authored-by: Peter <61538034+PTKu@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 22, 2023
1 parent d9e73a1 commit 5b13eec
Show file tree
Hide file tree
Showing 20 changed files with 505 additions and 12 deletions.
45 changes: 45 additions & 0 deletions docfx/articles/ltr/LTR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# LTR - LocalizablesToResx

LocalizablesToResx is a simple tool, which is used to acquire localized strings within `.razor` files and automatically generate `.resx` file based on these strings. This tool intends to simplify creation of .resx files from .razor files.


## Example

Let's have following .razor file with localized strings
```html

<h1>@Localizer["Home"]</h1>
<p>@Localizer["Welcome"]</p>
<p>@Localizer["This is example app"]</span>

```

We can call this script:

`ltr -f test.razor -o test.resx`

Following resx file will be generated:

![example](../../images/example-resx.png)

## Installation

dotnet tool install AXSharp.ltr --prerelease --local


## Parameters

This cli tool can be used with following parameters:

- -i (--identifier)
- identifier which represent localizer, which is used to locate localized strings (default is `Localizer`)
- not required
- -o (--output)
- output resx file
- mandatory
- -f (--file)
- source file, where localized string are located
- not required if -d is present
- -d (--directory)
- source directory, which files are enumerated and localized string are then located
- not required if -f is present
Binary file added docfx/images/example-resx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions src/AXSharp-L1-tests.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
"solution": {
"path": "AXSharp.sln",
"projects": [
"AXSharp.blazor\\tests\\sandbox\\ComponentsExamples\\ComponentsExamples.csproj",
"AXSharp.blazor\\tests\\sandbox\\AXSharp.RenderableContent.Tests\\AXSharp.RenderableContent.Tests.csproj",
"AXSharp.blazor\\tests\\sandbox\\ax-blazor-example\\ix\\ax-blazor-example.csproj",
"AXSharp.compiler\\tests\\AXSharp.CompilerTests\\AXSharp.CompilerTests.csproj",
"AXSharp.blazor\\tests\\sandbox\\ComponentsExamples\\ComponentsExamples.csproj",
"AXSharp.compiler\\tests\\AXSharp.Compiler.CsTests\\AXSharp.Compiler.CsTests.csproj",
"AXSharp.compiler\\tests\\AXSharp.CompilerTests\\AXSharp.CompilerTests.csproj",
"AXSharp.compiler\\tests\\AXSharp.ixc.Tests\\AXSharp.ixc.Tests.csproj",
"AXSharp.compiler\\tests\\AXSharp.ixr.Tests\\AXSharp.ixr.Tests.csproj",
"AXSharp.connectors\\tests\\AXSharp.ConnectorLegacyTests\\AXSharp.ConnectorLegacyTests.csproj",
"AXSharp.connectors\\tests\\AXSharp.ConnectorTests\\AXSharp.ConnectorTests\\AXSharp.ConnectorTests.csproj"
"AXSharp.connectors\\tests\\AXSharp.ConnectorTests\\AXSharp.ConnectorTests\\AXSharp.ConnectorTests.csproj",
"AXSharp.tools\\tests\\AXSharp.LocalizablesToResx.Tests\\AXSharp.LocalizablesToResx.Tests.csproj",
"AXSharp.tools\\tests\\AXSharp.nuget.update.Tests\\AXSharp.nuget.update.Tests.csproj"
]
}
}
3 changes: 2 additions & 1 deletion src/AXSharp-L2-tests.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"AXSharp.connectors\\tests\\ax-test-project\\ix\\ax_test_project.csproj",
"AXSharp.connectors\\tests\\exploring\\Webserver.Api.Exploratory\\Webserver.Api.Exploratory.csproj",
"AXSharp.connectors\\tests\\exploring\\exploratory.consoleapp\\exploratory.consoleapp.csproj",
"AXSharp.tools\\tests\\AXSharp.nuget.update.Tests\\AXSharp.nuget.update.Tests.csproj"
"AXSharp.tools\\tests\\AXSharp.nuget.update.Tests\\AXSharp.nuget.update.Tests.csproj",
"AXSharp.tools\\tests\\AXSharp.LocalizablesToResx.Tests\\AXSharp.LocalizablesToResx.Tests.csproj"
]
}
}
1 change: 1 addition & 0 deletions src/AXSharp-L3-tests.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"AXSharp.templates\\working\\templates\\ixconsole\\ixconsole.twin\\ixconsole.csproj",
"AXSharp.templates\\working\\templates\\ixconsole\\ixconsole\\ixconsole.app.csproj",
"AXSharp.templates\\working\\templates\\ixtwin\\ixtwin.csproj",
"AXSharp.tools\\tests\\AXSharp.LocalizablesToResx.Tests\\AXSharp.LocalizablesToResx.Tests.csproj"
"AXSharp.tools\\src\\AXSharp.nuget.update\\AXSharp.nuget.update.csproj",
"AXSharp.tools\\tests\\AXSharp.nuget.update.Tests\\AXSharp.nuget.update.Tests.csproj",
"sanbox\\integration\\ix-integration-blazor\\ix-integration-blazor.csproj",
Expand Down
3 changes: 2 additions & 1 deletion src/AXSharp-packable-only.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"AXSharp.compiler\\src\\ixd\\AXSharp.ixd.csproj",
"AXSharp.compiler\\src\\ixr\\AXSharp.ixr.csproj",
"AXSharp.connectors\\src\\AXSharp.Connector.S71500.WebAPI\\AXSharp.Connector.S71500.WebAPI.csproj",
"AXSharp.connectors\\src\\AXSharp.Connector\\AXSharp.Connector.csproj"
"AXSharp.connectors\\src\\AXSharp.Connector\\AXSharp.Connector.csproj",
"AXSharp.tools\\tests\\AXSharp.LocalizablesToResx.Tests\\AXSharp.LocalizablesToResx.Tests.csproj"
]
}
}
30 changes: 30 additions & 0 deletions src/AXSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ix_integration_plc", "sanbo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ax_blazor_example", "AXSharp.blazor\tests\sandbox\ax-blazor-example\ix\ax_blazor_example.csproj", "{6605983C-F5BF-4DD5-8F06-640505C0D6B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AXSharp.LocalizablesToResx", "AXSharp.tools\src\AXSharp.LocalizablesToResx\AXSharp.LocalizablesToResx.csproj", "{CC79093E-00B6-42A6-9548-E3A88494DC27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AXSharp.LocalizablesToResx.Tests", "AXSharp.tools\tests\AXSharp.LocalizablesToResx.Tests\AXSharp.LocalizablesToResx.Tests.csproj", "{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -530,6 +534,30 @@ Global
{6605983C-F5BF-4DD5-8F06-640505C0D6B0}.Release|x64.Build.0 = Release|Any CPU
{6605983C-F5BF-4DD5-8F06-640505C0D6B0}.Release|x86.ActiveCfg = Release|Any CPU
{6605983C-F5BF-4DD5-8F06-640505C0D6B0}.Release|x86.Build.0 = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|x64.ActiveCfg = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|x64.Build.0 = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|x86.ActiveCfg = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Debug|x86.Build.0 = Debug|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|Any CPU.Build.0 = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|x64.ActiveCfg = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|x64.Build.0 = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|x86.ActiveCfg = Release|Any CPU
{CC79093E-00B6-42A6-9548-E3A88494DC27}.Release|x86.Build.0 = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|x64.ActiveCfg = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|x64.Build.0 = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|x86.ActiveCfg = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Debug|x86.Build.0 = Debug|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|Any CPU.Build.0 = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|x64.ActiveCfg = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|x64.Build.0 = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|x86.ActiveCfg = Release|Any CPU
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -586,6 +614,8 @@ Global
{22F540F6-16FC-4C4A-9360-3FAB409807C2} = {02989C66-B1D1-4E5C-A350-6C3D18D0A6BD}
{60AEE1CA-8623-4950-BE53-196E00920888} = {F30D5DD0-F259-49E0-AB1B-84DD8766A37A}
{6605983C-F5BF-4DD5-8F06-640505C0D6B0} = {E7FC977B-1114-4D27-8FC8-DAB3F1323D24}
{CC79093E-00B6-42A6-9548-E3A88494DC27} = {FC332118-F8B8-48DC-A7EA-AC7559CD4A98}
{4C18C927-8CD6-4ED5-80F1-91DE60D2DD07} = {FE787422-9AB3-4065-A6D9-97511EC6141C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {99D50E81-8A37-4BB9-A435-C2C98430D600}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>


<!--NuGet Specific part-->
<Description>Creates Resx files from localizables in a .NET project.</Description>
<PackAsTool>True</PackAsTool>
<ToolCommandName>ltr</ToolCommandName>

<!-- NuGet Common part-->
<PackageProjectUrl>https://github.com/ix-ax/</PackageProjectUrl>
<RepositoryUrl>https://github.com/ix-ax/axsharp</RepositoryUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Authors>ix-ax</Authors>
<Copyright>(c) Peter Kurhajec and Contributors</Copyright>
<PackageTags>simatix-ax, PLC, industrial automation, SCADA, HMI</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Title>AX# compiler CLI</Title>
<PackageIcon>icon_128_128.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>
Release notes are published here:
https://github.com/ix-ax/axsharp/releases
</PackageReleaseNotes>
<PackageReadmeFile>NUGET-README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ResXResourceReader.NetStandard" Version="1.1.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="GitVersion.MsBuild" Version="5.10.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\..\assets\icons\icon_128_128.png" Link="icon_128_128.png">
<PackagePath>\</PackagePath>
<Pack>True</Pack>
</None>
<None Include="..\..\..\NUGET-README.md" Link="NUGET-README.md">
<PackagePath>\</PackagePath>
<Pack>True</Pack>
</None>
</ItemGroup>
<ItemGroup>
<None Update="tests\SimpleTests.razor">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions src/AXSharp.tools/src/AXSharp.LocalizablesToResx/Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CommandLine;

namespace AXSharp.LocalizablesToResx
{
public class Options
{
[Option('f', "file", Required = false, HelpText = "Source file, from which resx will be generated.")]
public string? SourceFile { get; set; }

[Option('d', "directory", Required = false, HelpText = "Source director, which contains files for resx gen.")]
public string? SourceDirectory { get; set; }

[Option('i', "identifier", Required = false, HelpText = "Localizable identifier, from which regex for searching is created. If empty, default value \"Localizer\" is used.")]
public string? Identifier { get; set; }

[Option('o', "output", Required = true, HelpText = "Required output resx file.")]
public string? OutputResx { get; set; }

}
}
161 changes: 161 additions & 0 deletions src/AXSharp.tools/src/AXSharp.LocalizablesToResx/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// See https://aka.ms/new-console-template for more information
using AXSharp.LocalizablesToResx;
using CommandLine;
using CommandLine.Text;
using System.Resources.NetStandard;
using System.Text.RegularExpressions;


Parser.Default.ParseArguments<Options>(args)
.WithParsed(o =>
{
Main(o);
});



void Main(Options o)
{
Console.WriteLine("** ltr (LocalizablesToResx) **");
Console.WriteLine("Generator of resx file from source code based on localizable identifier.");
Console.WriteLine("Generating...");
// Create a Regex
ResXGen.CreateRegex(o.Identifier);

// Create in memory dictionary with unique localizable values
ResXGen.CreateResxDictionary(o);

// Write dictionary into resx file
ResXGen.WriteToResx(o.OutputResx);

Console.WriteLine($"Returned {ResXGen.count} records.");
Console.WriteLine($"Location: {o.OutputResx}");
Console.WriteLine("Done.");


}

/// <summary>
/// Main static class containing logic of acquiring localized strings and generating resx file.
/// </summary>
public static class ResXGen
{
public static uint count;
public static Regex LocalizableRegex;
public static Dictionary<string, string> ResxDictionary = new Dictionary<string, string>();


/// <summary>
/// Create regex based on -i argument. If argument is empty, default regex "Localizer\[.*?\]" is used.
/// </summary>
/// <param name="identifier">Identifier used in regex</param>
public static void CreateRegex(string identifier)
{
string pattern;
if (identifier == null)
{
//use default identifier for regex
pattern = @"Localizer\[.*?\]";
}
else
{
pattern = $@"{identifier}\[.*?\]";
}

LocalizableRegex = new Regex(pattern);
}

/// <summary>
/// Creates dictionary of values acquired from input files.
/// </summary>
/// <param name="o"></param>
public static void CreateResxDictionary(Options o)
{
if (o.SourceFile != null)
{
if (!File.Exists(o.SourceFile))
{
Console.WriteLine("Source file does not exist!");
return;
}
else
{
AddLocalizablesToDictionary(o.SourceFile);
}
}

if (o.SourceDirectory != null)
{
if (!Directory.Exists(o.SourceDirectory))
{
Console.WriteLine("Director does not exist!");
return;
}
else
{
CreateResxDictionaryRecursive(o.SourceDirectory);
}
}

}

/// <summary>
/// Writes created dictionary of localizable values into resx file.
/// </summary>
/// <param name="outputPath">Output path of resx file</param>
public static void WriteToResx(string outputPath)
{
using (ResXResourceWriter resx = new ResXResourceWriter(outputPath))
{
foreach (var item in ResxDictionary)
{
resx.AddResource(item.Value, item.Value);
}
}
}

private static void CreateResxDictionaryRecursive(string sourceDir)
{
foreach (string d in Directory.GetDirectories(sourceDir))
{
foreach (string f in Directory.GetFiles(d, "*.razor"))
{
AddLocalizablesToDictionary(f);
}

CreateResxDictionaryRecursive(d);
}
}

private static void AddLocalizablesToDictionary(string filePath)
{
string ln;
using (StreamReader file = new StreamReader(filePath))
{
while ((ln = file.ReadLine()) != null)
{
MatchCollection matches = LocalizableRegex.Matches(ln);

foreach (Match match in matches)
{
var value = match.Value;

string[] sp = value.Split('\"');
// get text inside [""]
if (sp.Length > 1 && ResxDictionary.TryAdd(sp[1], sp[1]))
{
count++;
}
}

}
file.Close();
}
}


}


Loading

0 comments on commit 5b13eec

Please sign in to comment.