Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a multilingual domain model

I am using domain-driven design and I have a pretty clear picture of my domain model. It contains over 120 classes and it is quite stable. We will implement it in .NET 4 and C#. The thing is, we need the model to be multilingual; some of the attributes need to be stored in multiple languages. For example, the Person class has a Position property of type string which should store a value in English (e.g. "Librarian") and Spanish (e.g. "Bibliotecario"). The getter for this property should return the English or Spanish version depending on some language parameter.

And here begin my questions. I am not sure how to parameterize this. I have exlpored two major ways to do it:

  1. Use property collections. Position would not be a string but rather a Dictionary<Language, string>, which would let clients retrieve the person's position by language.
  2. Keep simple, scalar properties, but make them return (or set) the value for one language or another depending on a globally known "current language" setting. The client code would set the working language, and then all objects would set and get values in that language.

Option 1 avoids global state, but it messes up the interface of almost every class in my model. On the other hand, option 2 is less expressive, since you can't tell what language you're going to get without looking at the global setting. Also, it introduces a dependency into every class on the global setting.

Please note that I am not interested in database or ORM implementations; I am working at the domain model level only.

I have two specific questions then:

  • Which is the best option (1 or 2) to achieve my goal of a multilingual domain model?
  • Are there other options that I haven't considered, and which are they?

Thank you.

Edit. Some have suggested that this is a user interface related issue, and thus can be tackled through globalisation/localisation support in .NET. I disagree. UI localisation works only if you know the localised literals that must be shown to the user at compile time, but this is not our case. My question involves multilingual data that is unknown at compile time, because it will be provided as user data at run-time. This is not a UI-related issue.

Edit 2. Please bear in mind that the Person.Position is just a toy example to illustrate the question. It's not part of the real model. Don't try to criticise it or improve upon it; there is no point in doing that. Our business requirements involve a lot of attributes that cannot be encoded as enum types or similar, and must stay as free text. Hence the difficulty.

like image 257
CesarGon Avatar asked Feb 27 '13 15:02

CesarGon


2 Answers

Given the following:

Some use case involve setting the values for an object in all the supported languages; others involve looking at the values in one given language.

I would suggest going for both options. That means that the Person and all classes that hold multilingual content should keep that content in their state and:

  • The Position property should set/get the person's position in the current user's language.

  • There should be a corresponding property or method for all language setting/getting.

  • There should be a method for setting (or even switching if needed) the user language. I would create an abstract class (e.g. BaseMultilingualEntity) with an abstract SetLanguage(Language lang) method and a CurrentLanguage getter. You need to keep track of all the objects that derive from BaseMultilingualEntity in some sort of registry that would expose language setting.

EDIT WITH SOME CODE

public enum Language {
    English,
    German
}

// all multilingual entity classes should derive from this one; this is practically a partly implemented observer
public abstract class BaseMultilingualEntity
{
    public Language CurrentLanguage { get; private set; }

    public void SetCurrentLanguage(Language lang)
    {
        this.CurrentLanguage = lang;
    }
}

// this is practically an observable and perhaps SRP is not fully respected here but you got the point i think
public class UserSettings
{
    private List<BaseMultilingualEntity> _multilingualEntities;

    public void SetCurrentLanguage(Language lang)
    {
        if (_multilingualEntities == null)
            return;

        foreach (BaseMultilingualEntity multiLingualEntity in _multilingualEntities)
            multiLingualEntity.SetCurrentLanguage(lang);
    }

    public void TrackMultilingualEntity(BaseMultilingualEntity multiLingualEntity)
    {
        if (_multilingualEntities == null)
            _multilingualEntities = new List<BaseMultilingualEntity>();

        _multilingualEntities.Add(multiLingualEntity);
    }
}

// the Person entity class is a multilingual entity; the intention is to keep the XXXX with the XXXXInAllLanguages property in sync
public class Person : BaseMultilingualEntity
{
    public string Position
    {
        set
        {
            _PositionInAllLanguages[this.CurrentLanguage] = value;
        }
        get
        {
            return _PositionInAllLanguages[this.CurrentLanguage];
        }
    }

    private Dictionary<Language, string> _PositionInAllLanguages;

    public Dictionary<Language, string> PositionInAllLanguages {
        get
        {
            return _PositionInAllLanguages;
        }
        set
        {
            _PositionInAllLanguages = value;
        }
    }
}

public class Program
{
    public static void Main()
    {

        UserSettings us = new UserSettings();
        us.SetCurrentLanguage(Language.English);

        Person person1 = new Person();
        us.TrackMultilingualEntity(person1);

        // use case: set position in all languages
        person1.PositionInAllLanguages = new Dictionary<Language, string> {
            { Language.English, "Software Developer" }, 
            { Language.German, "Software Entwikcler" }
        };

        // use case: display a person's position in the user language
        Console.WriteLine(person1.Position);

        // use case: switch language
        us.SetCurrentLanguage(Language.German);
        Console.WriteLine(person1.Position);

        // use case: set position in the current user's language
        person1.Position = "Software Entwickler";

        // use case: display a person's position in all languages
        foreach (Language lang in person1.PositionInAllLanguages.Keys)
            Console.WriteLine(person1.PositionInAllLanguages[lang]);


        Console.ReadKey();

    }
}

like image 173
gtourkas Avatar answered Sep 21 '22 02:09

gtourkas


A domain model is an abstraction - it models a specific part of the world, it captures the concepts of a domain.

The model exists so developers can communicate in code the way domain experts communicate - using the same names for the same concepts.

Now, a Spanish expert and an English expert may use different words for the same concept, but the concept itself would be the same (one hopes, though language can be ambiguous and people do not always understand the same concept in the same way, but I digress).

The code should pick one human language for these concepts and stick to it. There is absolutely no reason for the model to consist of different languages in order to represent a single concept.

Now, you may need to show users of the application data and meta data in their language, but the concept does not change.

In this regard, you second option is the thing you should be doing - with .NET, this is normally done by looking at the CurrentThread.CurrentCulture and/or CurrentThread.CurrentUICulture and by using satellite assemblies that will contain localized resources.

like image 39
Oded Avatar answered Sep 22 '22 02:09

Oded