Skip to content

Commit

Permalink
Merge pull request #5 from dotnet-campus/t/walterlv/use-default-names…
Browse files Browse the repository at this point in the history
…pace

Let Uno Platform work
  • Loading branch information
lindexi authored Jun 15, 2024
2 parents efc9da4 + a233d6c commit 69e0c0f
Showing 22 changed files with 608 additions and 208 deletions.
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,3 +3,126 @@
| Build | NuGet |
|--|--|
|![](https://github.com/dotnet-campus/dotnetCampus.SourceLocalizations/workflows/.NET%20Core/badge.svg)|[![](https://img.shields.io/nuget/v/dotnetCampus.SourceLocalizations.svg)](https://www.nuget.org/packages/dotnetCampus.SourceLocalizations)|

dotnetCampus.SourceLocalizations is a source generator that can convert text localization files (e.g. .toml) into C# code and provide strong type support for localization keys.

## Features

```csharp
static void Main()
{
Console.WriteLine(LocalizedText.Current.App.Title); // "Hello, World!"
Console.WriteLine(LocalizedText.Current.App.Description); // "This is a sample application."
Console.WriteLine(LocalizedText.Current.Cli.Usage); // "Usage: dotnetCampus.SourceLocalizations [options]"
Console.WriteLine(LocalizedText.Current.PressAnyKeyToExit); // "Press any key to exit..."
}
```

- Source Generators
- [x] Generate C# codes
- [ ] Generate properties for implementation types (so that reflections on types can get localized properties which is very important for WPF Bindings)
- [ ] Generate localized types for each language item which contains more than one arguments (This fixes different argument orders among different languages.)
- File formats
- [x] TOML
- [x] YAML `🤡 Might be deprecated in the future.`
- UI Frameworks Support
- [x] Avalonia `😉 We look forward to your better suggestions.`
- [ ] MAUI `😶‍🌫️ Not tested yet`
- [x] Uno Platform `😉 We look forward to your better suggestions.`
- [ ] Wpf `😶‍🌫️ Not tested yet`
- Diagnostics Analyzers and Code Fixes
- [ ] Detect (and generate) missing localization keys
- [ ] Detect (and remove) unused localization keys
- [ ] Detect arguments mismatch among localized texts (e.g. `Hello, {name:string}` in en but `こんにちは、{errorCode:int}` in ja)
- [ ] Detect invalid IETF language tags and report errors

## Installation

[![](https://img.shields.io/nuget/v/dotnetCampus.SourceLocalizations.svg)](https://www.nuget.org/packages/dotnetCampus.SourceLocalizations)

```shell
dotnet add package dotnetCampus.SourceLocalizations
```

## Usage

### 1. Create localization files

```toml
// Localizations/en.toml
App.Title = "Hello, World!"
App.Description = "This is a sample application."
Cli.Usage = "Usage: dotnetCampus.SourceLocalizations [options]"
PressAnyKeyToExit = "Press any key to exit..."
```

```toml
// Localizations/zh-hans.toml
App.Title = "你好,世界!"
App.Description = "这是一个示例应用程序。"
Cli.Usage = "用法:dotnetCampus.SourceLocalizations [选项]"
PressAnyKeyToExit = "按任意键退出..."
```

The file name must conform to the [IETF BCP 47 standard](https://en.wikipedia.org/wiki/IETF_language_tag).

Add these files to your project `csproj` file:

```xml
<ItemGroup>
<AdditionalFiles Include="Localizations\**\*.toml" />
</ItemGroup>
```

### 2. Write a localization class

```csharp
// LocalizedText.cs
using dotnetCampus.SourceLocalizations;

namespace SampleApp;

// The default language is used to generate localization interfaces, so it must be the most complete one.
[LocalizedConfiguration(Default = "en", Current = "zh-hans")]
public partial class LocalizedText;
```

### 3. Use the generated code

```csharp
// Program.cs
static void Main()
{
Console.WriteLine(LocalizedText.Current.App.Title); // "Hello, World!"
Console.WriteLine(LocalizedText.Current.App.Description); // "This is a sample application."
Console.WriteLine(LocalizedText.Current.Cli.Usage); // "Usage: dotnetCampus.SourceLocalizations [options]"
Console.WriteLine(LocalizedText.Current.PressAnyKeyToExit); // "Press any key to exit..."
}
```

```xml
<!-- Avalonia MainWindow.axaml -->
<TextBlock Text="{Binding App.Title, Source={x:Static l:LocalizedText.Current}}" />
<TextBlock Text="{Binding App.Description, Source={x:Static l:LocalizedText.Current}}" />
```

```xml
<!-- Uno Platform MainPage.xaml -->
<TextBlock Text="{x:Bind l:Lang.Current.App.Title}" />
<TextBlock Text="{x:Bind l:Lang.Current.App.Description}" />
```

```csharp
// Uno Platform MainPage.xaml.cs
using dotnetCampus.Localizations;

namespace dotnetCampus.SampleUnoApp;

public sealed partial class MainPage : Page
{
public MainPage() => InitializeComponent();

// IMPORTANT: The Lang property must be public.
public ILocalizedValues Lang => global::dotnetCampus.SampleUnoApp.Localizations.LocalizedText.Current;
}
```
9 changes: 9 additions & 0 deletions dotnetCampus.SourceLocalizations.sln
Original file line number Diff line number Diff line change
@@ -24,6 +24,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6E0F9A76
build\Version.props = build\Version.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.SourceLocalizations.PrivateGenerator", "src\dotnetCampus.SourceLocalizations.PrivateGenerator\dotnetCampus.SourceLocalizations.PrivateGenerator.csproj", "{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A585D7EF-916E-45B1-AFB8-025DE9FD1647}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -49,9 +53,14 @@ Global
{3170B151-6A25-4321-BF09-EFC516E32F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3170B151-6A25-4321-BF09-EFC516E32F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3170B151-6A25-4321-BF09-EFC516E32F8D}.Release|Any CPU.Build.0 = Release|Any CPU
{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F330175F-FC20-4D42-921A-747AC296D1B9} = {E407F54C-8E41-4F4E-B9BF-9864B6178E5F}
{3170B151-6A25-4321-BF09-EFC516E32F8D} = {4239BEE3-D480-4874-83E1-353B35F1BD86}
{452ACCD4-EB0A-46ED-A8F9-38B5AD57C9EB} = {A585D7EF-916E-45B1-AFB8-025DE9FD1647}
EndGlobalSection
EndGlobal
14 changes: 7 additions & 7 deletions samples/LocalizationSample/Program.cs
Original file line number Diff line number Diff line change
@@ -15,13 +15,13 @@ public static void Main(string[] args)
internal partial class Lang;

[EditorBrowsable(EditorBrowsableState.Never)]
public interface ILocalized_Root : ILocalizedStringProvider
public interface ILocalizedValues : ILocalizedStringProvider
{
ILocalized_Root_A A => (ILocalized_Root_A)this;
ILocalizedValues_A A => (ILocalizedValues_A)this;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public interface ILocalized_Root_A : ILocalizedStringProvider
public interface ILocalizedValues_A : ILocalizedStringProvider
{
LocalizedString A1 => this.Get0("A.A1");

@@ -30,8 +30,8 @@ public interface ILocalized_Root_A : ILocalizedStringProvider
LocalizedString<object> A3 => this.Get1<object>("A.A3");
}

public class Lang_ZhHans(ILocalized_Root? fallback) : ILocalized_Root,
ILocalized_Root_A
public class Lang_ZhHans(ILocalizedValues? fallback) : ILocalizedValues,
ILocalizedValues_A
{
private readonly FrozenDictionary<string, string> _strings = new Dictionary<string, string>
{
@@ -45,8 +45,8 @@ public class Lang_ZhHans(ILocalized_Root? fallback) : ILocalized_Root,
public string IetfLanguageTag => "zh-hans";
}

public class Lang_En(ILocalized_Root? fallback) : ILocalized_Root,
ILocalized_Root_A
public class Lang_En(ILocalizedValues? fallback) : ILocalizedValues,
ILocalizedValues_A
{
private readonly FrozenDictionary<string, string> _strings = new Dictionary<string, string>
{
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ namespace dotnetCampus.Localizations.Assets.Analyzers;
/// </summary>
/// <param name="ietfLanguageTag"></param>
/// <param name="fallback"></param>
public class LspPlaceholder(string ietfLanguageTag, ILocalized_Root? fallback) : ILocalized_Root
public class LspPlaceholder(string ietfLanguageTag, ILocalizedValues? fallback) : ILocalizedValues
{
/// <inheritdoc />
public string IetfLanguageTag => ietfLanguageTag;
Original file line number Diff line number Diff line change
@@ -6,10 +6,10 @@
namespace dotnetCampus.Localizations.Assets.Templates;

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public interface ILocalized_Root : ILocalizedStringProvider
public partial interface ILocalizedValues : ILocalizedStringProvider
{
// <FLAG>
// ILocalized_Root_A A => (ILocalized_Root_A)this;
// ILocalizedValues A => (ILocalizedValues_A)this;
// LocalizedString A1 => this.Get0("A.A1");
// </FLAG>
}
Original file line number Diff line number Diff line change
@@ -7,10 +7,10 @@ partial class Localization
/// <summary>
/// 获取默认的本地化字符串集。
/// </summary>
public static ILocalized_Root Default { get; } = new LspPlaceholder("default", null);
public static ILocalizedValues Default { get; } = new LspPlaceholder("default", null);

/// <summary>
/// 获取当前的本地化字符串集。
/// </summary>
public static ILocalized_Root Current { get; private set; } = new LspPlaceholder("current", null);
public static ILocalizedValues Current { get; private set; } = new LspPlaceholder("current", null);
}
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ namespace dotnetCampus.Localizations.Assets.Templates;

/// <inheritdoc />
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public class LocalizationValues(ILocalized_Root? fallback) : ILocalized_Root
public class LocalizationValues(ILocalizedValues? fallback) : ILocalizedValues
{
/// <inheritdoc />
public string IetfLanguageTag => "default";
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public string ToInterfaceCodeText(string rootNamespace) => $"""
using ILocalizedStringProvider = global::dotnetCampus.Localizations.ILocalizedStringProvider;
using LocalizedString = global::dotnetCampus.Localizations.LocalizedString;
namespace {rootNamespace}.Localizations;
namespace {GeneratorInfo.RootNamespace};
{RecursiveConvertLocalizationTreeNodeToKeyInterfaceCode(Tree, 0)}
""";

@@ -57,8 +57,8 @@ private string RecursiveConvertLocalizationTreeNodeToKeyInterfaceCode(Localizati
}

var nodeTypeName = depth is 0
? "Root"
: "Root_" + string.Join("_", node.FullIdentifierKey);
? ""
: "_" + string.Join("_", node.FullIdentifierKey);
var propertyLines = node.Children.Select(x =>
{
var identifierKey = string.Join("_", x.FullIdentifierKey);
@@ -86,13 +86,13 @@ private string RecursiveConvertLocalizationTreeNodeToKeyInterfaceCode(Localizati
}
else
{
return $" ILocalized_Root_{identifierKey} {x.IdentifierKey} => (ILocalized_Root_{identifierKey})this;";
return $" ILocalizedValues_{identifierKey} {x.IdentifierKey} => (ILocalizedValues_{identifierKey})this;";
}
});
return $$"""
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public interface ILocalized_{{nodeTypeName}} : ILocalizedStringProvider
public{{(depth is 0 ? " partial" : "")}} interface ILocalizedValues{{nodeTypeName}} : ILocalizedStringProvider
{
{{string.Join("\n", propertyLines)}}
}
@@ -120,11 +120,11 @@ public string ToImplementationCodeText(string rootNamespace, string ietfLanguage
var typeName = IetfLanguageTagToIdentifier(ietfLanguageTag);
var template = GeneratorInfo.GetEmbeddedTemplateFile<LocalizationValues>();
var code = template.Content
.Replace($"namespace {template.Namespace};", $"namespace {rootNamespace}.Localizations;")
.Replace($"namespace {template.Namespace};", $"namespace {GeneratorInfo.RootNamespace};")
.Replace($"class {nameof(LocalizationValues)}", $"class {nameof(LocalizationValues)}_{typeName}")
.Replace(
$" : ILocalized_Root",
$" : ILocalized_Root{string.Concat(EnumerateConvertTreeNodeToInterfaceNames(Tree.Children).Select(x => $",\n ILocalized_Root_{x}"))}")
$" : ILocalizedValues",
$" : ILocalizedValues{string.Concat(EnumerateConvertTreeNodeToInterfaceNames(Tree.Children).Select(x => $",\n ILocalizedValues_{x}"))}")
.Replace("""IetfLanguageTag => "default";""", $"""IetfLanguageTag => "{ietfLanguageTag}";""");
var lines = LocalizationItems.Select(x => ConvertKeyValueToValueCodeLine(x.Key, x.Value));
code = TemplateRegexes.FlagRegex.Replace(code, string.Concat(lines));

This file was deleted.

This file was deleted.

Loading

0 comments on commit 69e0c0f

Please sign in to comment.