Simple package to localize strings from json files via static source code generation.
Implemented via C# source generators
Also see example project
Add a Nuget package reference to the project file in the project you want to localize:
<PackageReference Include="kli.Localize" Version="<version>" />
Example:
{
"SampleText": "FooBar",
"Other": "Text42"
}
Give your default localization a name without specifying the culture (e.g. Locale.json
). All other localizations follow the pattern <Filename>_<CultureInfo.Name>.json
(e.g. Locale_en-US.json
for American English or Locale_en.json
for English)
In an ItemGroup
in your csproj file add an AdditionFiles
element for each default localization json file. Set the Include
attribute to the path of the file.
Example:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="kli.Localize" Version="0.7.*" />
<AdditionalFiles Include="TestLocalizations\Locale.json" />
</ItemGroup>
</Project>
This means: if you have a Locale.json
and a Locale_en-US.json
you only have to add the Locale.json
as <AdditionalFiles>
. You can add as many files as you want.
Now you should be able to locate the generated source code in your project under Dependencies/Analyzers.
Of course you can also view and debug the generated source code.
Generated code example
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by kli.Localize.Generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace kli.Localize.Example.Localizations
{
using System;
using System.Globalization;
using System.Collections.Generic;
using Translations = System.Collections.Generic.Dictionary<string, string>;
public sealed class Locale
{
private static readonly LocalizationProvider provider = new LocalizationProvider();
public static IDictionary<string, string> GetAll(CultureInfo cultureInfo = null) => provider.GetValues(cultureInfo ?? CultureInfo.CurrentUICulture);
public static string GetString(string key, CultureInfo cultureInfo = null) => provider.GetValue(key, cultureInfo ?? CultureInfo.CurrentUICulture);
///<summary>Similar to: Hallo Welt (German)</summary>
public static string MyText => provider.GetValue(nameof(MyText), CultureInfo.CurrentUICulture);
private class LocalizationProvider
{
delegate bool SelectorFunc<T>(Translations translations, out T arg);
internal string GetValue(string key, CultureInfo cultureInfo)
{
bool ValueSelector(Translations translations, out string value)
{
if (translations.TryGetValue(key, out value))
return true;
value = key;
return false;
}
return TraverseCultures<string>(cultureInfo, ValueSelector);
}
internal IDictionary<string, string> GetValues(CultureInfo cultureInfo)
{
bool ValueSelector(Translations translations, out Translations value)
{
value = translations;
return true;
}
return TraverseCultures<Translations>(cultureInfo, ValueSelector);
}
private T TraverseCultures<T>(CultureInfo cultureInfo, SelectorFunc<T> selectorFunc)
{
if (resources.TryGetValue(cultureInfo, out Translations translations))
{
if (selectorFunc(translations, out T result) || cultureInfo == CultureInfo.InvariantCulture)
return result;
}
return TraverseCultures<T>(cultureInfo.Parent, selectorFunc);
}
private static readonly Translations invariant = new()
{{"MyText", "Hallo Welt (German)"}, };
private static readonly Translations en = new()
{{"MyText", "Hello World (English)"}, };
private static readonly Dictionary<CultureInfo, Translations> resources = new()
{{CultureInfo.InvariantCulture, invariant}, {new CultureInfo("en"), en}, };
}
}
}
Import the namespace where you put your *.json files and use the generated code to access your localizations.
Access is based on CultureInfo.CurrentUICulture
The namespace is generated using the following pattern:
rootnamespace + relative directory structure
It is now possible to override the namespace and the class/file name that will be generated:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="kli.Localize" Version="0.8.*" />
<AdditionalFiles Include="Localizations\Locale.json"
NamespaceName="Namesapce.of.your.choice"
ClassName="MyClassName" />
</ItemGroup>
</Project>
From which the following is generated:
namespace Namesapce.of.your.choice
{
...
public sealed class MyClassName {
...
Directly after including the package sometimes the tooling (Visual Studio) gets stuck. If you encounter any problems with source generation try to restart Visual Studio and/or check the build log for warnings/errors.
Feel free to create an Issue