Class library localization
This article describes how to localize a class library. There is a sample library on github.
A solid structure for a class library is to organize files by feature. Under each feature there are the related the files, classes, resources, assets, localization. For example:
ClassLibrary/ ├── Feature/ │ ├── Class.l.yaml │ └── Class.cs └── Feature/ ├── Class.l.yaml └── Class.cs
Let's create an example class library samples.library.
The Localization.Default is pre-configured to look for embedded resources under "*/{Key}" pattern, thereof localization file can be placed under the feature "ItemManagement" directory.
samples.library/ └── ItemManagement/ ├── Manifest.l.yaml └── Manifest.cs
Note how the embedded resources are named as [AssemblyName].[Folders].[FileName] in the assembly.
├── samples.library/ │ └── samples.library/samples.library.ItemManagement.Manifest.l.yaml
The keys in the localization file, here Manifest.l.yaml, must adhere to the same naming notation.
TemplateFormat: Brace
PluralRules: Unicode.CLDR41
Invariant:
- Culture: ""
Items:
- Key: samples.library.ItemManagement.Manifest.Header
Text: "Identity | Count "
- Key: samples.library.ItemManagement.Manifest.Spacer
Text: "--------------------"
- Key: samples.library.ItemManagement.Manifest.Line
Text: "{Identity,8} | {Count,9}"
Finnish:
- Culture: fi
Items:
- Key: samples.library.ItemManagement.Manifest.Header
Text: "Nimike | Lukumäärä"
- Key: samples.library.ItemManagement.Manifest.Spacer
Text: "--------------------"
- Key: samples.library.ItemManagement.Manifest.Line
Text: "{Identity,8} | {Count,9}"
Localized keys are available within the class Manifest.cs.
namespace samples.library;
using Avalanche.Localization;
using System.Globalization;
using System.Text;
using static System.Console;
public class Manifest : List<Manifest.Item>
{
/// <summary>Manifest item</summary>
public record Item(string Identity, int Count);
/// <summary>Header text</summary>
static ILocalizableText Header = Localization.Default.LocalizableText["samples.library.ItemManagement.Manifest.Header"];
/// <summary>Spacer</summary>
static ILocalizableText Spacer = Localization.Default.LocalizableText["samples.library.ItemManagement.Manifest.Spacer"];
/// <summary>Line</summary>
static ILocalizableText Line = Localization.Default.LocalizableText["samples.library.ItemManagement.Manifest.Line"];
/// <summary>Print items in manifest in language specified in <paramref name="format"/>.</summary>
public string ToString(IFormatProvider format)
{
//
StringBuilder sb = new StringBuilder();
// Append header
Header.AppendTo(sb, format); sb.AppendLine();
// Append spacer
Spacer.AppendTo(sb, format); sb.AppendLine();
// Append each item
foreach (Item item in this)
{
Line.AppendTo(sb, format, new object[] { item.Identity, item.Count });
sb.AppendLine();
}
// Print table
return sb.ToString();
}
/// <summary>Print items in manifest. Uses current thread's UI culture.</summary>
public override string ToString() => ToString(CultureInfo.CurrentUICulture);
/// <summary>Test run for debugging.</summary>
public static void Run()
{
// Create manifest
Manifest manifest = new Manifest();
manifest.Add(new Item("#5435", 10));
manifest.Add(new Item("#9639", 20));
// Print manifest
WriteLine(manifest.ToString(CultureInfo.GetCultureInfo("en")));
// Print manifest
WriteLine(manifest.ToString(CultureInfo.GetCultureInfo("fi")));
/*
Identity | Count
--------------------
#5435 | 10
#9639 | 20
Nimike | Lukumäärä
--------------------
#5435 | 10
#9639 | 20
*/
}
}
Considerations
There are few deployment considerations for class libraries:
- How to consume localization resources within the class library.
- How to expose configuration from the application deployment into the class library.
- How to supply localization resources internally from inside the class library.
- How to supply localizations externally after the library has been built.
- How to customize localization from class library.
The Localization.Default singleton addresses considerations #1 - #4.
ILocalizableText text = Localization.Default.LocalizableTextCached["Namespace.Apples"];
If class library needs to customize localization (#5), it can use internal method that returns localization after customizations are completed.
ILocalizableText text = Facade.Localization.LocalizableTextCached["Namespace.Apples"];
public class Facade
{
/// <summary>Library specific localization</summary>
static ILocalization? localization;
/// <summary>Get localization with customizations</summary>
public static ILocalization Localization
{
get
{
// Get reference
ILocalization? _localization = localization;
// Return reference
if (_localization != null) return _localization;
//
lock (typeof(Facade))
{
// Get reference again
_localization = localization;
// Return reference
if (_localization != null) return _localization;
// Configure
_localization = Avalanche.Localization.Localization.Default
.AddFileSystemWithPattern(
new LocalizationFileSystemEmbedded(typeof(Facade).Assembly),
"library/library.embedded.{Key}",
"library/library.embedded.{Key}");
// Return
return localization = _localization;
}
}
}
}
Dependency Injection
If class library is designed to be used with dependency injection, then it should have ITextLocalizer, IStringLocalizer or ILocalization injected into class constructor or from service provider reference. Dependency injection is intended to be configured from file and from deploying application and class library cannot address considerations #5.
Class Library:
public class MyService
{
ITextLocalizer<MyService> localizer;
public MyService(ITextLocalizer<MyService> localizer)
{
this.localizer = localizer;
}
}
Deploying Application:
// Add service descriptors
IServiceCollection serviceCollection = new ServiceCollection()
.AddAvalancheLocalizationService()
.AddAvalancheLocalizationFileSystemApplicationRoot()
.AddSingleton<MyService>();
// Build service
using ServiceProvider service = serviceCollection.BuildServiceProvider();
// Get service
MyService myService = service.GetRequiredService<MyService>();