Skip to content

Overview#

Beta feature#

Coverage is not 100%. Help output and common use cases have been prioritized. Configuration errors for developers are not included at this time as it's assumed the app will be tested before being given to users who wouldn't be able to act on the errors anyway. We will accept PR's if this is important to you.

Submit feedback via GitHub Issues, GitHub Discussions or Discord

We will also need help with translations and will accept PRs for those translations.

TLDR, How to enable#

Enable the feature by setting appSettings.Localize to a Func<string,string?> such as text => stringLocalizer[text]. Every package that supports localization will detect this during registration.

static int Main(string[] args)
{
    IStringLocalizer localizer = ConfigureLocalizer();
    var settings = new AppSettings{ Localization = t => localizer[t] };
    return new AppRunner<ValidationApp>(settings).Run(args);
}

Advanced cases#

To use a different Func<string,string?> per package, supply the appropriate ResourcesProxy in the AppRunner constructor and in the registration for each package.

static int Main(string[] args)
{
    IDictionary<string,IStringLocalizer> localizers = ConfigureLocalizers();
    var proxy = new ResourcesProxy{ Localization = t => localizers["core"][t] };
    return new AppRunner<ValidationApp>(settings, 
            new ResourcesProxy{ Localization = t => localizers["core"][t] })
        .UseDataAnnotationValidations(args, 
            new DataAnnotations.ResourcesProxy{ Localization = t => localizers["validation"][t] })
        .Run(args);
}

To use a more custom approach, override the appropriate Resources class in each package and supply them during registration.

class MyResources : Resources
{
    public override string Error_File_not_found(string fullPath) => $"missing file: {fullPath}";
    ...
}

static int Main(string[] args)
{
    return new AppRunner<ValidationApp>(settings, new MyResources()).Run(args);
}

Implementation#

Goals#

  • provide localization
  • easy to use
  • extensible
  • avoid the cost of translation lookups when not needed
  • common entries available for reuse in other middleware
  • easy to identify new entries
  • fallback to default when localized values are not found

Resources and ResourcesProxy#

Every package that contains localizable content will contain, in the root namespace, a Resources class and a ResourcesProxy that takes a Func<string,string>. Each package will have it's own implementation of Resources and ResourcesProxy. For example, DataAnnotations and FluentValidation have their own versions.

As seen in the examples above, the IStringLocalizer indexer can be used for Func<string,string?>, making this approach extensible without taking a dependency on localization extensions.

We chose to use a class instead of interface for Resources to avoid every addition being a breaking change. Localization should not cause your app to fail and should not force you to recompile code for every update. We've provided tooling to help you identify when new resources are added via unit testing.

Testing#

See Localization testing to see how we ensure proxies include every member and how you can generate and test your own.

See the culture directive for a way to set the culture for a command. Enable for unit tests and manual testing.

Generating Resource Files#

There are examples in ResourceGenerators

Basic resx and json translations files can be found in the localization_files folder.

Warning

Some keys differ only in case, eg. Arguments & arguments, which is not supported by the VS resx editor. The ResourceManager should honor case by default.

If you'd like to see another format supported, submit a PR generating the file in ResourceGenerators and it will be updated when new keys are added.

Back to top