kli.Localize by Tobias Klimm
NuGet / site data
Details
Info
Name: kli.Localize
Generates statically accessible code for .json files that serve as a source for localization (C# Source Generators)
Author: Tobias Klimm
NuGet: https://www.nuget.org/packages/kli.Localize/
You can find more details at https://github.com/kl1mm/localize
Author
Tobias Klimm
Original Readme
Localize
Simple package to localize strings from json files via static source code generation.
Implemented via C# source generators
Usage
Also see example project
Install the nuget package
Add a Nuget package reference to the project file in the project you want to localize:
<PackageReference Include="kli.Localize" Version="<version>" />
Create *.json files for your localized texts.
Example:
{
"SampleText": "FooBar",
"Other": "Text42",
"NestedItems": {
"Are": "also supported"
}
}
Name your localization files, including the culture according to the pattern: <FileName>_<CultureInfo.Name>.json
(e.g. Locale_de.json
).
For other cultures follow the same pattern (e.g. Locale_en-US.json
for American English or Locale_en.json
for English).
You have to specify which given culture is the neutral culture (the fallback used if there is no localization found for a specific culture)
via the NeutralCulture
attribute on the Localize
element.
Add json files to csproj
In an ItemGroup
in your csproj file add an Localize
element for each localization json file, include other cultures via glob pattern.
Set the Include
attribute to the path of the file and specify the neutral culture via the NeutralCulture
attribute
Example:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="kli.Localize" Version="1.0.*" />
<Localize Include="TestLocalizations\Locale_*.json" NeutralCulture="en"/>
</ItemGroup>
</Project>
This means: if you have a Locale_en.json
and a Locale_en-US.json
add Locale_*.json
as <Localize>
, and specify either en
or en_US
as NeutralCulture
.
Add other files the same way in another Localize
element.
Use it in your code
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
Namespace generation
The namespace is generated using the following pattern:
rootnamespace + relative directory structure
This behaviour can be overridden with the NamespaceName
attribute on the Localize
element.
Class naming
The default class name in the generated code is the filename without the culture.
This behaviour can be overridden with the ClassName
attribute on the Localize
element.
Changelog
Help! Why is no code generated?
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.
Need help? Problems?
Feel free to create an Issue
About
Generating locality files from json files
How to use
Example (source csproj, source files)
- CSharp Project
- Program.cs
- AllText_en.json
- AllText_ro.json
This is the CSharp Project that references kli.Localize
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="kli.Localize" Version="2.0.4" />
<Localize Include="TestData\AllText_*.json" NeutralCulture="en"/>
</ItemGroup>
<!--<ItemGroup>
<CompilerVisibleProperty Include="FileEmbed_MaxEmbedSize" />
</ItemGroup>
<PropertyGroup>
<FileEmbed_MaxEmbedSize>SIZE_IN_BYTES_GOES_HERE</FileEmbed_MaxEmbedSize>
</PropertyGroup>-->
</Project>
This is the use of kli.Localize in Program.cs
using EmbedDemo.TestData;
using System.Globalization;
using System.Text;
Console.WriteLine("Hello, World!");
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
Console.WriteLine(AllText.Welcome);
CultureInfo.CurrentUICulture = new CultureInfo("ro-RO");
Console.WriteLine(AllText.Welcome);
Console.WriteLine(AllText.page1.Text);
This is the use of kli.Localize in AllText_en.json
{
"Welcome": "Welcome , user",
"page1": {
"Text": "List of Persons"
}
}
This is the use of kli.Localize in AllText_ro.json
{
"Welcome": "Bine ai venit",
"page1": {
"Text": "Lista persoanelor"
}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- AllText.g.cs
//------------------------------------------------------------------------------
// <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 EmbedDemo.TestData
{
using System;
using System.Globalization;
using System.Collections.Generic;
using Translations = System.Collections.Generic.Dictionary<string, string>;
///<summary>Localizations from "AllText_en.json"</summary>
public sealed partial class AllText
{
private static readonly LocalizationProvider provider = new LocalizationProvider();
/// <summary>
/// Retrieves all translations for the specified culture.
/// </summary>
/// <param name="cultureInfo">The culture for which translations are retrieved. Defaults to the current UI culture.</param>
/// <returns>A dictionary containing all translations for the specified culture.</returns>
public static IDictionary<string, string> GetAll(CultureInfo cultureInfo = null) => provider.GetValues(cultureInfo ?? CultureInfo.CurrentUICulture);
/// <summary>
/// Retrieves a localized string for the specified key and culture.
/// </summary>
/// <param name="key">The key of the string to retrieve.</param>
/// <param name="cultureInfo">The culture for which the string is retrieved. Defaults to the current UI culture.</param>
/// <returns>The localized string if found, traversing through parent cultures. Otherwise, the key itself.</returns>
public static string GetString(string key, CultureInfo cultureInfo = null) => provider.GetValue(key, cultureInfo ?? 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()
{
{
"Welcome",
"Welcome , user"
},
{
"page1::Text",
"List of Persons"
},
};
private static readonly Translations ro = new()
{
{
"Welcome",
"Bine ai venit"
},
{
"page1::Text",
"Lista persoanelor"
},
};
private static readonly Dictionary<CultureInfo, Translations> resources = new()
{
{
CultureInfo.InvariantCulture,
invariant
},
{
new CultureInfo("ro"),
ro
},
};
}
///<summary>Similar to: Welcome , user</summary>
public static string Welcome => provider.GetValue("Welcome", CultureInfo.CurrentUICulture);
///<summary>Localizations for "page1" from "AllText_en.json"</summary>
public static class page1
{
///<summary>Similar to: List of Persons</summary>
public static string Text => provider.GetValue("page1::Text", CultureInfo.CurrentUICulture);
}
}
}
Useful
Download Example (.NET C#)
Share kli.Localize
https://ignatandrei.github.io/RSCG_Examples/v2/docs/kli.Localize
Category "FilesToCode" has the following generators:
1 Chorn.EmbeddedResourceAccessGenerator
3 Datacute.EmbeddedResourcePropertyGenerator
7 LingoGen
13 RSCG_Utils
17 Weave