Slang.Net by Egor Zheludkov
NuGet / site data
Details
Info
Name: Slang.Net
Type-safe i18n for .NET
Author: Egor Zheludkov
NuGet: https://www.nuget.org/packages/Slang.Net/
You can find more details at https://github.com/egorozh/Slang.NET/
Author
Egor Zheludkov

Original Readme
![]()
Slang.NET
Type-safe i18n for .NET
About this library
Slang.NET is a .NET port of the slang from the Dart/Flutter community with new features (like string format).
You can view how the generated files general, en, and de look
Getting Started:
Install the library as a NuGet package:
Install-Package dotnet add package Slang.Net
######### Add JSON files:
Important file must end with ".i18n.json". This is necessary so that the SourceGenerator does not track changes to other AdditionalFiles.
i18n/strings_en.i18n.json or i18n/strings_en-US.i18n.json or i18n/strings.i18n.json (for base culture)
Note The locale part of the file name is
language[-Script][-COUNTRY], so a script subtag is supported as well:i18n/strings_sr-Cyrl-RS.i18n.json,i18n/strings_sr-Latn-RS.i18n.json,i18n/strings_zh-Hant.i18n.json.
{
"screen": {
"locale1": "Locale 1"
}
}
i18n/strings_ru.i18n.json or i18n/strings_ru-RU.i18n.json
{
"screen": {
"locale1": "Локаль 1"
}
}
slang.json
{
"base_culture": "en" // or "en-EN"
}
Recommendation It is recommended to specify the country code, such as "en-US," for proper functionality when formatting strings, especially if you will be retrieving the list of cultures from SupportedCultures.
######### Include JSON files as AdditionalFiles:
<ItemGroup>
<AdditionalFiles Include="i18n\*.i18n.json" />
<AdditionalFiles Include="slang.json" />
</ItemGroup>
######### Add a partial class:
[Translations(InputFileName = "strings")]
public partial class Strings;
######### Done!
Strings.SetCulture(new CultureInfo("ru-RU"));
Console.WriteLine(Strings.Instance.Root.Screen.Locale1); // Локаль 1
Strings.SetCulture(new CultureInfo("en-US"));
Console.WriteLine(Strings.Instance.Root.Screen.Locale1); // Locale 1
or
<MenuItem Header="{Binding Root.Screen.Locale1, Source={x:Static localization:Strings.Instance}}" />
Features
- String Interpolation
- Typed Parameters
- Comments
- String Format
- Pluralization
- Linked Translations
- Lists
- Maps
######### String Interpolation
You can specify parameters passed at runtime..
{
"Hello": "Hello {name}"
}
The generated code will look like this:
/// In en, this message translates to:
/// **"Hello {name}"**
public virtual string Hello(object name) => $"Hello {name}";
######### Typed Parameters
Parameters are typed as object by default. This is convenient because it offers maximum flexibility.
You can specify the type using two syntax options: 1 - Simple:
{
"greet": "Hello {name: string}, you are {age: int} years old"
}
2 - Using a placeholders, which allows you to specify a type or format string (see String Format).
{
"greet2": "Hello {name}, you are {age} years old",
"@greet2": {
"placeholders": {
"name": {
"type": "string"
},
"age": {
"type": "int"
}
}
}
}
The generated code will look like this:
/// In ru, this message translates to:
/// **"Hello {name}, you are {age} years old"**
public virtual string Greet(string name, int age) => $"Hello {name}, you are {age} years old";
######### Comments
You can add comments to your translation files.
{
"@@locale": "en", // fully ignored
"mainScreen": {
"button": "Submit",
// ignored as translation but rendered as a comment
"@button": "The submit button shown at the bottom",
// or use
"button2": "Submit",
"@button2": {
"description": "The submit button shown at the bottom"
}
}
}
The generated code will look like this:
/// The submit button shown at the bottom
///
/// In ru, this message translates to:
/// **"Submit"**
public virtual string Button => "Submit";
######### String Format
This library supports embedding format via ToString(format) for the following types: int, long, double,
decimal, float, DateTime, DateOnly, TimeOnly, TimeSpan. For other types, the format string is passed through
string.Format(format, locale).
{
"dateExample": "Date {date}",
"@dateExample": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "dd MMMM HH:mm"
}
}
}
}
String s = Strings.Instance.Root.DateExample(DateTime.Now); // Date 17 October 22:25
The generated code will look like this:
/// In ru, this message translates to:
/// **"Date {date}"**
public virtual string DateExample(DateTime date)
{
string dateString = date.ToString("dd MMMM HH:mm");
return $"Date {dateString}";
}
######### Pluralization
This library uses the concept defined here.
Some languages have support out of the box. See here.
Plurals are detected by the following keywords: zero, one, two, few, many, other.
{
"someKey": {
"apple": {
"one": "I have {n} apple.",
"other": "I have {n} apples."
}
}
}
String a = Strings.Instance.Root.SomeKey.Apple(n: 1); // I have 1 apple.
String b = Strings.Instance.Root.SomeKey.Apple(n: 2); // I have 2 apples.
The generated code will look like this:
public virtual string Apple(int n) => PluralResolvers.Cardinal("en")(n,
one: $"I have {n} apple.",
other: $"I have {n} apples.");
The detected plurals are cardinals by default.
To specify ordinals, you need to add the (ordinal) modifier.
{
"someKey": {
"apple(cardinal)": {
"one": "I have {n} apple.",
"other": "I have {n} apples."
},
"place(ordinal)": {
"one": "{n}st place.",
"two": "{n}nd place.",
"few": "{n}rd place.",
"other": "{n}th place."
}
}
}
By default, the parameter name is n. You can change that by adding a modifier.
{
"someKey": {
"apple(param=appleCount)": {
"one": "I have one apple.",
"other": "I have multiple apples."
}
}
}
String a = Strings.Instance.Root.SomeKey.Apple(appleCount: 1); // notice 'appleCount' instead of 'n'
You can set the default parameter globally using PluralParameter.
[Translations(
InputFileName = "strings",
PluralParameter = "count")]
internal partial class Strings;
######### Linked Translations
You can link one translation to another. Add the prefix @: followed by the absolute path to the desired
translation.
{
"fields": {
"name": "my name is {firstName}",
"age": "I am {age} years old"
},
"introduce": "Hello, @:fields.name and @:fields.age"
}
String s = Strings.Instance.Root.Introduce(firstName: "Tom", age: 27); // Hello, my name is Tom and I am 27 years old.
The generated code will look like this:
/// In ru, this message translates to:
/// **"Hello, {_root.Fields.Name(firstName: firstName)} and {_root.Fields.Age(age: age)}"**
public virtual string Introduce(object firstName, object age) => $"Hello, {_root.Fields.Name(firstName: firstName)} and {_root.Fields.Age(age: age)}";
Optionally, you can escape linked translations by surrounding the path with {}:
{
"fields": {
"name": "my name is {firstName}"
},
"introduce": "Hello, @:{fields.name}inator"
}
######### Lists
You can also place lists inside lists!
{
"niceList": [
"hello",
"nice",
[
"first item in nested list",
"second item in nested list"
],
{
"wow": "WOW!",
"ok": "OK!"
},
{
"aMapEntry": "access via key",
"anotherEntry": "access via second key"
}
]
}
String a = Strings.Instance.Root.NiceList[1]; // "nice"
String b = Strings.Instance.Root.NiceList[2][0]; // "first item in nested list"
String c = Strings.Instance.Root.NiceList[3].Ok; // "OK!"
String d = Strings.Instance.Root.NiceList[4].AMapEntry; // "access via key"
The generated code will look like this:
public virtual List<dynamic> NiceList => [
"hello",
"nice",
new[]{
"first item in nested list",
"second item in nested list",
},
new Feature1NiceList0i3Ru(_root),
new Feature1NiceList0i4Ru(_root),
];
######### Maps
You can access each translation using string keys.
Add the (map) modifier.
{
"a(map)": {
"helloWorld": "hello"
},
"b": {
"b0": "hey",
"b1(map)": {
"hiThere": "hi"
}
}
}
Now you can access translations using keys:
String a = Strings.Instance.Root.A["helloWorld"]; // "hello"
String b = Strings.Instance.Root.B.B0; // "hey"
String c = Strings.Instance.Root.B.B1["hiThere"]; // "hi"
The generated code will look like this:
/// In ru, this message translates to:
/// **"hey"**
public virtual string B0 => "hey";
public virtual IReadOnlyDictionary<string, string> B1 => new Dictionary<string, string> {
{"hiThere", "hi"},
};
Slang CLI
######### Translate with GPT
Take advantage of GPT to internationalize your app with context-aware translations.
Install Slang CLI:
dotnet tool install --global Slang.CLI
Then add the following configuration in your slang.json:
{
"base_culture": "en",
"gpt": {
"base_culture": "ru",
"model": "gpt-4o-mini",
"description": "Showcase for Slang.Net.Gpt"
}
}
Then use slang-gpt:
slang gpt --target=en --api-key=<api-key>
See more: [Documentation](https://github.com/egorozh/Slang.NET/Utilities/Gpt/README.md
Additional Materials
######### Articles
######### Videos
About
Generate localization files from strings
How to use
Example (source csproj, source files)
- CSharp Project
- AllText.i18n.json
- AllText_ro.i18n.json
- Trans.cs
- Program.cs
This is the CSharp Project that references Slang.Net
<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>
<AdditionalFiles Include="i18n\*.i18n.json" />
<AdditionalFiles Remove="i18n\AllText_ro-Ro.i18n.json" />
<AdditionalFiles Include="slang.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Slang.Net" Version="10.1.1" />
</ItemGroup>
</Project>
This is the use of Slang.Net in AllText.i18n.json
{
"page1": {
"MyText": "List of Persons"
}
}
This is the use of Slang.Net in AllText_ro.i18n.json
{
"page1": {
"MyText": "Lista persoanelor"
}
}
This is the use of Slang.Net in Trans.cs
using Slang;
namespace EmbedDemo;
[Translations(InputFileName = "AllText")]
public partial class TranslateAllText;
This is the use of Slang.Net in Program.cs
using EmbedDemo;
using System.Globalization;
Console.WriteLine("Hello, World!");
TranslateAllText.SetCulture(new CultureInfo("en-US"));
Console.WriteLine(TranslateAllText.Instance.Root.Page1.MyText);
TranslateAllText.SetCulture(new CultureInfo("ro-RO") );
Console.WriteLine(TranslateAllText.Instance.Root.Page1.MyText);
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- TranslateAllText.g.cs
- TranslateAllText_en-US.g.cs
- TranslateAllText_ro.g.cs
// Generated file. Do not edit.
//
//
// Locales: 2
//
// Built on 20.09.2026 at 16:12 UTC
#nullable enable
using Slang;
using EmbedDemo;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace EmbedDemo
{
partial class TranslateAllText
{
private static readonly CultureInfo _en_US = new CultureInfo("en-US");
private static readonly CultureInfo _ro = new CultureInfo("ro");
private static readonly Dictionary<CultureInfo, TranslateAllText> _translations =
new Dictionary<CultureInfo, TranslateAllText>(capacity: 2)
{
{_en_US, new TranslateAllText() },
{_ro, new TranslateAllTextRo() }
};
public static CultureInfo BaseCulture => _en_US;
public static IReadOnlyList<CultureInfo> SupportedCultures => _translations.Keys.ToList();
public static TranslationsInstance Instance \{ get; \} = new TranslationsInstance();
public static void SetCulture(CultureInfo culture, bool uiOnly = false)
{
if (!uiOnly)
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
Instance.OnCultureChanged();
}
public class TranslationsInstance : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public TranslateAllText Root
{
get
{
var culture = CultureInfo.CurrentUICulture;
if (_translations.TryGetValue(culture, out var translation))
return translation;
var sameCulture = SupportedCultures.FirstOrDefault(c => c.TwoLetterISOLanguageName == culture.TwoLetterISOLanguageName);
if (sameCulture != null)
return _translations[sameCulture];
return _translations[BaseCulture];
}
}
public void OnCultureChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Root)));
}
}
}
}
#nullable enable
using Slang;
using EmbedDemo;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace EmbedDemo
{
partial class TranslateAllText
{
protected virtual TranslateAllText _root \{ get; \} // ignore: unused_field
public TranslateAllText()
{
_root = this;
Page1 = new TranslateAllTextPage1EnUs(_root);
}
// Translations
public virtual TranslateAllTextPage1EnUs Page1 \{ get; }
// Path: Page1
public class TranslateAllTextPage1EnUs
{
public TranslateAllTextPage1EnUs(TranslateAllText root)
{
this._root = root;
}
protected virtual TranslateAllText _root \{ get; \} // ignore: unused_field
// Translations
/// In en, this message translates to:
/// **"List of Persons"**
public virtual string MyText => "List of Persons";
}
}
}
#nullable enable
using Slang;
using EmbedDemo;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace EmbedDemo
{
partial class TranslateAllTextRo : TranslateAllText
{
protected override TranslateAllTextRo _root \{ get; \} // ignore: unused_field
public TranslateAllTextRo()
{
_root = this;
Page1 = new TranslateAllTextPage1Ro(_root);
}
// Translations
public override TranslateAllTextPage1Ro Page1 \{ get; }
// Path: Page1
public class TranslateAllTextPage1Ro : TranslateAllTextPage1EnUs
{
public TranslateAllTextPage1Ro(TranslateAllTextRo root) : base(root)
{
this._root = root;
}
protected override TranslateAllTextRo _root \{ get; \} // ignore: unused_field
// Translations
/// In ro, this message translates to:
/// **"Lista persoanelor"**
public override string MyText => "Lista persoanelor";
}
}
}
Useful
Download Example (.NET C#)
Share Slang.Net
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Slang.Net
Category "FilesToCode" has the following generators:
1 Chorn.EmbeddedResourceAccessGenerator
2024-01-21
2 corecraft
2024-02-17
3 Datacute.EmbeddedResourcePropertyGenerator
2024-11-03
4 DotnetYang
2024-06-29
5 EmbedResourceCSharp
2023-04-16
6 kli.Localize
2025-10-01
7 LingoGen
2024-02-23
8 Maestria.TypeProviders
2026-04-09
9 MagicConstants
2026-09-04
10 NFH.FileEmbed
2025-08-08
11 NotNotAppSettings
2024-01-26
12 Pinecone.TypedPath
2026-08-24
13 Podimo.ConstEmbed
2023-04-16
14 ResXGenerator
2023-10-02
15 RSCG_JSON2Class
2024-02-29
16 RSCG_Utils
2023-04-16
17 Slang.Net
2026-09-11
18 Strings.ResourceGenerator
2025-07-06
19 SvgIconGenerator
2026-04-04
20 ThisAssembly_Resources
2023-09-16
21 ThisAssembly.Strings
2024-07-21
22 TypedPaths
2026-04-01
23 Weave
2024-01-27