Skip to content

Commit

Permalink
feat: Add Support for Nested Classes #8
Browse files Browse the repository at this point in the history
  • Loading branch information
kl1mm committed Sep 17, 2024
1 parent 9720f6c commit 4be1fa2
Show file tree
Hide file tree
Showing 25 changed files with 739 additions and 350 deletions.
49 changes: 46 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Example:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="kli.Localize" Version="0.7.*" />
<PackageReference Include="kli.Localize" Version="1.0.*" />

<AdditionalFiles Include="TestLocalizations\Locale.json" />
</ItemGroup>
Expand Down Expand Up @@ -146,9 +146,52 @@ Access is based on [CultureInfo.CurrentUICulture](https://docs.microsoft.com/en-
The namespace is generated using the following pattern:<br>
`rootnamespace + relative directory structure`
<br>
Since v0.8 this behaviour can be overriden [see 'From version 0.8'](#From version 0.8)

## Version Changes

### From version 1.0

#### BREAKING - Ignore none JSON-String/Object values
All properties that are not string or object will be ignored.
```json
{
"Number": 4.2,
"Bool": true,
"Null": null,
"Array": [1,2,3]
}
```

#### [Add Support for Nested Classes #8](https://github.com/kl1mm/localize/issues/8)
It is now possible to use JSON objects in the localization files.
During generation, the structure is mapped as a nested class for access

```json
{
"SomeText": "some text",
"Sub":
{
"FileNotFound": "Not found",
"DivideByZero": "x / zero"
},
"UI":{
"LabelOne": "One",
"LabelTwo": "Two",
"Login": {
"LabelUserName": "User",
"LabelPassword": "Pass"
}
}
}
```
#### Improved Diagnostics
- SGL0001: InvalidJsonFileFormat - `<JsonReaderException.Message>`
- SGL0002: InvalidJsonPropertyName - `Json property key must be a valid C# identifier`
- SGL0003: InvalidJsonTokenType - `Json property value must be an object or a string`

All diagnostics came with LinePostion (linenumber & column)

### From version 0.8

It is now possible to override the namespace and the class/file name that will be generated:
Expand All @@ -158,14 +201,14 @@ It is now possible to override the namespace and the class/file name that will b
<PackageReference Include="kli.Localize" Version="0.8.*" />

<AdditionalFiles Include="Localizations\Locale.json"
NamespaceName="Namesapce.of.your.choice"
NamespaceName="Namespace.of.your.choice"
ClassName="MyClassName" />
</ItemGroup>
</Project>
```
From which the following is generated:
```csharp
namespace Namesapce.of.your.choice
namespace Namespace.of.your.choice
{
...
public sealed class MyClassName {
Expand Down
2 changes: 1 addition & 1 deletion example/kli.Localize.Example/kli.Localize.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
31 changes: 0 additions & 31 deletions src/kli.Localize.Generator/Internal/CultureData.cs

This file was deleted.

33 changes: 0 additions & 33 deletions src/kli.Localize.Generator/Internal/GeneratorDataContext.cs

This file was deleted.

41 changes: 41 additions & 0 deletions src/kli.Localize.Generator/Internal/Helper/CultureData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace kli.Localize.Generator.Internal.Helper
{
internal class CultureData
{
public const string InvariantKeyName = "invariant";

public string Key { get; private set; }
public string NormalizedKey { get; private set; }
public TranslationData Translations { get; private set; }

private CultureData() { }

public static IReadOnlyList<CultureData> Initialize(string filePath, ITranslationReader translationReader)
{
var searchDir = Path.GetDirectoryName(filePath)!;
var fileName = Path.GetFileNameWithoutExtension(filePath);
return Directory.GetFiles(searchDir, $"{fileName}*{Path.GetExtension(filePath)}")
.Select(cfp => ResolveCulture(cfp, translationReader)).ToList();
}

private static CultureData ResolveCulture(string cultureFilePath, ITranslationReader translationReader)
{
var cultureId = Path.GetFileNameWithoutExtension(cultureFilePath).Split('_').Skip(1).LastOrDefault()
?? InvariantKeyName;

return new()
{
Key = cultureId,
NormalizedKey = NormalizeCultureIdentifier(cultureId),
Translations = translationReader.Read(cultureFilePath)
};
}

private static string NormalizeCultureIdentifier(string cultureId)
=> cultureId.ToLower().Replace('-', '_');
}
}
16 changes: 16 additions & 0 deletions src/kli.Localize.Generator/Internal/Helper/GeneratorData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Linq;

namespace kli.Localize.Generator.Internal.Helper
{
internal class GeneratorData
{
public IReadOnlyList<CultureData> CultureData { get; set; }
public TranslationData InvariantTranslationData
=> this.CultureData.Single(cd => cd.Key == Helper.CultureData.InvariantKeyName).Translations;

public string Namespace { get; set; }
public string GeneratedFileName { get; set; }
public string GeneratedClassName { get; set; }
}
}
18 changes: 18 additions & 0 deletions src/kli.Localize.Generator/Internal/Helper/GeneratorDataBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.CodeAnalysis;

namespace kli.Localize.Generator.Internal.Helper;

internal class GeneratorDataBuilder(AdditionalText originFile,
NamesResolver namesResolver, ITranslationReader translationReader)
{
public GeneratorData Build()
{
return new GeneratorData
{
GeneratedClassName = namesResolver.ResolveGeneratedClassName(),
GeneratedFileName = namesResolver.ResolveGeneratedFileName(),
Namespace = namesResolver.ResolveNamespace(),
CultureData = CultureData.Initialize(originFile.Path, translationReader),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace kli.Localize.Generator.Internal
namespace kli.Localize.Generator.Internal.Helper
{
internal class NamesResolver
{
Expand All @@ -22,7 +22,7 @@ public string ResolveGeneratedClassName()
{
if (this.optionsProvider.GetOptions(this.originFile).TryGetValue("build_metadata.AdditionalFiles.ClassName", out var className) && !string.IsNullOrWhiteSpace(className))
return className;
return Path.GetFileNameWithoutExtension(originFile.Path);
return Path.GetFileNameWithoutExtension(this.originFile.Path);
}

public string ResolveGeneratedFileName()
Expand All @@ -34,7 +34,7 @@ public string ResolveNamespace()
return namespaceName;

if (!this.optionsProvider.GlobalOptions.TryGetValue("build_property.rootnamespace", out var rootNamespace))
rootNamespace = fallBackRootNamespace;
rootNamespace = this.fallBackRootNamespace;

if (this.optionsProvider.GlobalOptions.TryGetValue("build_property.projectdir", out var projectDir))
{
Expand Down
28 changes: 28 additions & 0 deletions src/kli.Localize.Generator/Internal/Helper/TranslationData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;

namespace kli.Localize.Generator.Internal.Helper
{
internal class TranslationData : Dictionary<string, object>
{
public IDictionary<string, string> Flatten()
=> this.FlattenCore(this, string.Empty);

private IDictionary<string, string> FlattenCore(TranslationData translationData, string parentKey)
{
var result = new Dictionary<string, string>();
foreach (var kvp in translationData)
{
var key = string.IsNullOrEmpty(parentKey) ? kvp.Key : $"{parentKey}::{kvp.Key}";
if (kvp.Value is TranslationData nested)
{
foreach (var nestedKvp in this.FlattenCore(nested, key))
result[nestedKvp.Key] = nestedKvp.Value;
}
else
result[key] = kvp.Value.ToString();
}

return result;
}
}
}
9 changes: 9 additions & 0 deletions src/kli.Localize.Generator/Internal/ITranslationReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using kli.Localize.Generator.Internal.Helper;

namespace kli.Localize.Generator.Internal
{
internal interface ITranslationReader
{
TranslationData Read(string filePath);
}
}
39 changes: 39 additions & 0 deletions src/kli.Localize.Generator/Internal/Json/JsonDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;

namespace kli.Localize.Generator.Internal.Json
{
internal static class JsonDiagnostics
{
public static void ReportInvalidFileFormat(this Action<Diagnostic> reporter, string filePath, JsonReaderException ex)
{
reporter.Report(1, ex.Message, filePath,
new LinePosition(ex.LineNumber-1, ex.LinePosition), DiagnosticSeverity.Error);
}

public static void ReportInvalidKey(this Action<Diagnostic> reporter, string filePath, JsonTextReader reader)
{
var message = $"Json property key must be a valid C# identifier: '{reader.Value}'";
reporter.Report(2, message, filePath,
new LinePosition(reader.LineNumber-1, reader.LinePosition), DiagnosticSeverity.Warning);
}

public static void ReportInvalidTokenType(this Action<Diagnostic> reporter, string filePath, JsonTextReader reader)
{
var message = $"Json property value must be an object or a string: '{reader.TokenType} - {reader.Value} ({reader.ValueType})'";
reporter.Report(3, message, filePath,
new LinePosition(reader.LineNumber-1, reader.LinePosition), DiagnosticSeverity.Warning);
}

private static void Report(this Action<Diagnostic> reporter, int id, string message,
string filePath, LinePosition linePosition, DiagnosticSeverity severity)
{
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor($"SGL000{id}", "kli.Localize.Generator", message, "Source Generators", severity, true),
Location.Create(filePath, new TextSpan(), new LinePositionSpan(linePosition, linePosition)));
reporter(diagnostic);
}
}
}
Loading

0 comments on commit 4be1fa2

Please sign in to comment.