TypealizR 0.8.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package TypealizR --version 0.8.2
NuGet\Install-Package TypealizR -Version 0.8.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TypealizR" Version="0.8.2">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TypealizR --version 0.8.2
#r "nuget: TypealizR, 0.8.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TypealizR as a Cake Addin
#addin nuget:?package=TypealizR&version=0.8.2

// Install TypealizR as a Cake Tool
#tool nuget:?package=TypealizR&version=0.8.2

build Coverage Status CodeQL Publish

Quality Gate Status Lines of Code Duplicated Lines (%) Code Smells Maintainability Rating Security Rating Bugs Vulnerabilities Reliability Rating Technical Debt

Samples STS Samples LTS

NuGet NuGet (preview)

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

  • install via NuGet
  • modify target csproj (where those precious ResX-files are ;P)
<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 demo_typealize_translation_initial

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 TypealizRs 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'

demo_typed_parameters

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]:

demo_groups

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
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.9.5 1,654 11/6/2023
0.9.4 1,255 9/19/2023
0.9.3 764 5/13/2023
0.9.2 666 2/5/2023
0.9.2-pre0008 747 2/5/2023
0.8.4 327 1/20/2023
0.8.2 274 12/31/2022
0.8.1 285 12/30/2022
0.8.0 248 12/30/2022
0.8.0-pre0005 262 12/30/2022
0.7.1 299 12/27/2022