Table of Contents

LocalizableString type

The type GroupeIsa.Neos.Shared.Localization.LocalizableString is a string that can be localized into multiple languages.
See the LocalizableString API documentation for more information.

LocalizableString is immutable, which means that once it is created, it cannot be changed.

This type can be used for entity properties and it is persisted as a JSON object in the database.
This type is transpiled into a record type in typescript.

Breaking changes in version 2.2

Before version 2.2, the LocalizableString type was inherited from ReadOnlyDictionarty<string, string>. This is no longer the case. In version 2.2, the LocalizableString implementation use composition instead of inheritance in order to support oData select and expand queries.

This difference should create some breaking changes in the way you use the LocalizableString type, below are the changes you should be aware of:

Enumerating the LocalizableString

In version prior to 2.2, you could enumerate the LocalizableString as a ReadOnlyDictionary<string, string>. This is no longer possible.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

foreach (var localizedString in localizableString) // This will not compile
{
}

You should modify your code to use the LocalizableString methods to enumerate the localized strings.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

foreach (var localizedString in localizableString.AsEnumerable())
{
}

Another example using the Select method from the System.Linq namespace.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

var languages = localizableString.Where(ls => ls.Value.Length > 100).Select(ls => ls.Key) // This will not compile

You should modify your code to use AsEnumerable method.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

var languages = localizableString.AsEnumerable().Where(ls => ls.Value.Length > 100).Select(ls => ls.Key)

Convert the LocalizableString to a dictionary

In version 2.2 and later, an implicit operator converts the localizableString into a dictionary.

However, you should explicitly convert the LocalizableString to a dictionary using the ToDictionary method in some case where implicit conversion is not possible.

For example using the BeEquivalentTo method from the FluentAssertions library.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

localizableString.Should().BeEquivalentTo(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
}); // This will compile but will fail at runtime

you should modify your code to use the ToDictionary method.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

localizableString.ToDictionary().Should().BeEquivalentTo(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

you can also cast the LocalizableString to a dictionary.

var localizableString = new LocalizableString(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

((Dictionary<string, string>)localizableString).Should().BeEquivalentTo(new Dictionary<string, string>
{
    { "en", "Hello" },
    { "fr", "Bonjour" }
});

Getting a LocalizableString from a resource on server side

When the server sends text to the client that is displayed to the user, it is crucial to use LocalizableString as users can use different languages.

Ideally, texts should be created as translated C# resources or string resources in Neos Studio. Then you can create a LocalizableString from the resource using the following methods:

ILocalizationSettings _localizationSettings; // Injected

LocalizableString localizableString1 = LocalizableString.FromResource(
    _localizationSettings,
    Resources.MyModule.ResourceManager,
    nameof(Resources.MyModule.MyCaption));
string caption1 = localizableString1["en"];

string name = "Richard";
int age = 41;
LocalizableString localizableString2 = LocalizableString.FromFormattedResource(
    _localizationSettings,
    Resources.MyModule.ResourceManager,
    nameof(Resources.MyModule.MyCaptionWithFormat),
    name,
    age);
string caption2 = localizableString2.GetTranslationOrDefault("fr");

Additionally, you can use resources extension methods GetLocalizableString and GetFormattedLocalizableString provided by namespace GroupeIsa.Neos.Domain :

using GroupeIsa.Neos.Domain;

...

ILocalizationSettings _localizationSettings; // Injected

LocalizableString localizableString1 = Resources.MyModule.GetLocalizableString(
    _localizationSettings,
    nameof(Resources.MyModule.MyCaption));
string caption1 = localizableString1.GetTranslationOrDefault("en");

string name = "Richard";
int age = 41;
LocalizableString localizableString2 = Resources.MyModule.GetFormattedLocalizableString(
    _localizationSettings,
    nameof(Resources.MyModule.MyCaptionWithFormat),
    name,
    age);
string caption2 = localizableString2["fr"];

Localization settings required by these methods can be injected with dependency injection:

public class MyClass
{
    private readonly ILocalizationSettings _localizationSettings;

    public MyClass(ILocalizationSettings localizationSettings)
    {
        _localizationSettings = localizationSettings;
    }
}

Your class needs to be registered as a service in a Startup.cs file of your module:

/// <summary>
/// Represents the assembly startup.
/// </summary>
[ExcludeFromCodeCoverage]
public static class Startup
{
    /// <summary>
    /// Configures services for dependency injection.
    /// </summary>
    /// <param name="services">The services collection.</param>
    /// <remarks>
    /// This method is automatically called when the assembly is loaded.
    /// </remarks>
    public static void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<MyClass>();
    }
}