TypealizR 0.8.4
Prefix ReservedSee the version list below for details.
dotnet add package TypealizR --version 0.8.4
NuGet\Install-Package TypealizR -Version 0.8.4
<PackageReference Include="TypealizR" Version="0.8.4"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add TypealizR --version 0.8.4
#r "nuget: TypealizR, 0.8.4"
// Install TypealizR as a Cake Addin #addin nuget:?package=TypealizR&version=0.8.4 // Install TypealizR as a Cake Tool #tool nuget:?package=TypealizR&version=0.8.4
TypealizR
The typed internationalizeR
Statically typed i18n support for the .NET - ecosystem
usage
✔️ DO this:
@inject IStringLocalizer<HomePage> localize;
@inject AppUser user;
<h1>@localize.Title()<h1>
<h2>@localize.Welcome_back__userName(user.GivenName)<h2>
❌ DON´T do that:
@inject IStringLocalizer<HomePage> localize;
@inject AppUser user;
<h1>@localize["Title"]<h1>
<h2>@localize["Welcome back, {0}", user.GivenName]<h2>
getting started
<PropertyGroup>
<AdditionalFileItemNames>$(AdditionalFileItemNames);EmbeddedResource</AdditionalFileItemNames>
</PropertyGroup>
- rebuild target csproj
NOTE: visual-studio might need a fresh restart after installing (or updating) TypealizR in order to work as expected
- start utilizing statically typed resources
how it works
TypealizR parses ordinary Resx-files and generates extension-classes and -methods using source-generators
on the fly.
given the following folder-structure:
root/
+---/Pages/
+---/HomePage.razor
+---/HomePage.resx
+---/HomePage.en-EN.resx
+---/HomePage.de.resx
where HomePage.resx
looks like this:
key | value |
---|---|
Title | Home |
Welcome back, {userName}, this is your {visitCount:i} visit | Welcome back, {0}, this is your {1} visit to the app |
Good bye, {userName:s} | See you later, {0} |
TypealizR emits the following class (comments, usings, etc. omitted):
internal static class IStringLocalizerExtensions_Root_Pages_HomePage
{
public static string Title(
this IStringLocalizer<Root.Pages.HomePage> that)
=> that["Title"];
public static string Welcome_back__userName_this_is_your__visitCount__visit(
this IStringLocalizer<Root.Pages.HomePage> that, object userName, int visitCount)
=> that["Welcome back, {0}, this is your {1} visit to the app", userName, visitCount];
public static string Good_bye__userName(
this IStringLocalizer<Root.Pages.HomePage> that, string userName)
=> that["See you later, {0}", userName];
}
which then can be used in favor of the lesser-typed default-syntax of IStringLocalizer<T>
type-annotations ftw
TypealizR assists in spotting translations where specified arguments may mismatch by type / order.
✔️ DO this:
Consider the following call, which might have been wrong right from the start or just became wrong over time.
When applying type-annotations to the parameters, these kind of bugs can be prevented and addressed at compile-time when combined with TypealizR
s generated extension methods!
some.resx
<data name="Hello {user:s}, it is {today:d}" xml:space="preserve"> <value>Hello {0}, today is {1}</value> </data>
somecode.cs
var userName = "Arthur"; var today = DateOnly.FromDateTime(DateTimeOffset.Now.UtcDateTime); localize.Hello__user__it_is__today(today, userName); // wrong ordering, which would result in the translated string "Hello 2022-01-01, today is Arthur" // equivalent of localize["Hello {user:s}, it is {today:d}", today, userName];
With applied type-annotations, this will generate tho following compile-time errors:
- CS1503 Argument 2: cannot convert from 'System.DateOnly' to 'string'
- CS1503 Argument 3: cannot convert from 'string' to 'System.DateOnly'
❌ DON´T do that:
There's no way the default usage of IStringLocalizer
would discover such things this early in the dev-cycle!
some.resx
<data name="Hello {user}, it is {today}" xml:space="preserve"> <value>Hello {0}, today is {1}</value> </data>
somecode.cs
var userName = "Arthur"; var today = DateOnly.FromDateTime(DateTimeOffset.Now.UtcDateTime); localize["Hello {user}, it is {today}", today, userName]; // wrong parameter-ordering, which would result in the translated string "Hello 2022-01-01, today is Arthur"
Groupings
Grouping resources allows to semantically tie together resources in a meaningful way.
To group resources, prepend resource-keys with [Some.Nested.Group.Name]:
SomeResource.resx
<data name="[Messages.Warnings]: Attention}" xml:space="preserve"> <value>Attention Message</value> </data> <data name="[Messages.Warnings]: {Operation:s} failed" xml:space="preserve"> <value>Operation '{0}' failed</value> </data>
✔️ DO this:
Imperative usage
Wherever code may depend on IStringLocalizer<T>
, you can do this:
IStringLocalizer<SomeResource> localizer...; //wherever that instance might came from, most probably through dependency-injection
var typealized = localizer.Typealize(); //call the generated extension-method, which returns a type exposing groups as properties
//start using groups
Console.WriteLine(typealized.Messages.Warnings.Attention);
// "Attention Message"
Console.WriteLine(typealized.Messages.Warnings.Operation__failed("some operation name");
// "Operation 'some operation name' failed"
The generated classes are currently duck-typing IStringLocalizer<T>
.
This is done to support gradually adopting the benefits of statically typed localizations, while still beeing able to use the lesser typed default way of using IStringLocalizer<T>
during the course of adoption.
IStringLocalizer<SomeResource> localizer...;
var typealized = localizer.Typealize();
void SomeMethod(IStringLocalizer<SomeResource> localizer) {
//use localizer
}
SomeMethod(typealized.Localizer); //still works
Even ordinary usage is still possible:
IStringLocalizer<SomeResource> localizer...;
var typealized = localizer.Typealize();
localizer["[Messages.Warnings]: {Operation:s} failed", "some operation"];
typealized["[Messages.Warnings]: {Operation:s} failed", "some operation"]; //still works
Microsoft.Extensions.DependencyInjection
manual setup
//normal setup
var services = new ServiceCollection();
services.AddLogging();
services.AddLocalization();
//register typealized resource
services.AddScoped(x => x.GetRequiredService<IStringLocalizer<Resources>>().Typealize());
var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
//service-located typealized instance (or better just inject it somewhere)
var typealized = scope.ServiceProvider.GetRequiredService<TypealizedResources>();
The generated types are placed in a seperated namespace to prevent collisions with other types.
Given a *.resx
-file with the following FullName:
Some\Folder\Path\Resources.resx
The generated type will be
Some.Folder.Path.TypealizR.TypealizedResources
automatic setup
tbd. There might be a built-in solution for utilizing
IServiceCollection
to register typealized instances, once #63 is done.
❌ DON'T DO this:
All groups are still available as extension-methods for IStringLocalizer<T>
as a list of flat members.
IStringLocalizer<SomeResource> localize...;
Console.WriteLine(localize.MessagesWarnings_Attention());
// "Attention Message"
Console.WriteLine(localize.MessagesWarnings_Operation__failed("some operation name");
// "Operation 'some operation name' failed"
extensibilty
customize string formatting
Starting with v0.6, TypealizR supports customizing the internal usage of string.Format()
, which should enable developers to implement #16 with the technology / library / approach of their choice - Leaving TypelaziR un-opinionated about the actual approach to achieve this.
To customize the formatting, just drop a custom implementation of TypealizR_StringFormatter
anywhere in the project. The types namespace
MUST match the project´s root-namespace.
example
Given the root-namespace TypealizR.Ockz
for the project consuming TypealizR, this partial-class declaration should be enough:
namespace TypealizR.Ockz;
internal static partial class TypealizR_StringFormatter
{
internal static partial string Format(string s, object[] args) =>
new(string.Format(s, args).Reverse().ToArray());
}
With this implementation, every localized string would be reversed. (Even if that doesn´t make any sense ;P)
configuration
customize warnings
During code-generation, the code-generator
might emit one of these diagnostics.
To modify the severity of each reported diagnostics, provide a .globalconfig
-file in the root directory of the project which consumes TypealizR
.
samples
To ignore all diagnostics emitted by TypealizR
, provide the following content to .globalconfig
:
is_global = true
dotnet_diagnostic_TR0002_severity = hidden
dotnet_diagnostic_TR0003_severity = hidden
dotnet_diagnostic_TR0004_severity = hidden
To treat all diagnostics emitted by TypealizR
as a compiler-error, supply the following contents:
is_global = true
dotnet_diagnostic_TR0002_severity = error
dotnet_diagnostic_TR0003_severity = error
dotnet_diagnostic_TR0004_severity = error
See
- global-analyzerconfig for further details about analyzer-configs.
- #12 for details about design-decisssions
- #35 for implementation-details
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 4.3.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on TypealizR:
Package | Downloads |
---|---|
TypealizR.Analyzers
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.10.0-pre0013 | 70 | 10/30/2024 |
0.10.0-pre0012 | 68 | 10/29/2024 |
0.10.0-pre0011 | 77 | 10/26/2024 |
0.10.0-pre0010 | 71 | 10/26/2024 |
0.9.9 | 2,725 | 5/15/2024 |
0.9.8 | 116 | 5/15/2024 |
0.9.7 | 575 | 5/15/2024 |
0.9.6 | 114 | 5/14/2024 |
0.9.5 | 1,870 | 11/6/2023 |
0.9.4 | 1,299 | 9/19/2023 |
0.9.3 | 779 | 5/13/2023 |
0.9.2 | 744 | 2/5/2023 |
0.9.2-pre0008 | 873 | 2/5/2023 |
0.8.4 | 352 | 1/20/2023 |
0.8.2 | 303 | 12/31/2022 |
0.8.1 | 299 | 12/30/2022 |
0.8.0 | 263 | 12/30/2022 |
0.8.0-pre0005 | 286 | 12/30/2022 |
0.7.1 | 315 | 12/27/2022 |