1. Home
  2. Multilingual decision table

Multilingual decision table

◷ Reading Time: 8 minutes

Scope

When business rules are used in many different locales (countries in which different languages are spoken), then it makes sense to model and designs them in a way that multilingual concepts apply, to the following topics:

  1. Notification messages
  2. Values (string values)
  3. Options (option for values in the glossary)

Let’s consider the following simple business rules:

If Accounting Concept is Payable
then write Account concept is cash

As it is highlighted, the two parts of the rule must support multilingual.

Sample business rules

Let’s assume we want to model this rule in two languages – English and Spanish.

English

Let’s model this rule in a decision table that has an input parameter named ac:

In XML this will be modeled as follows:

<DecisionTable name="cash decision table">
    <Columns>
        <Condition name="Accounting Concept" expression="ac == $value" />
        <Action name="Write Message" expression="$value" type="Notice" />
    </Columns>
    <Data>
        <Row>
            <Value>Payable</Value>
            <Value>Account concept is cash</Value>
        </Row>
    </Data>
</DecisionTable>

Spanish

Let’s model this rule in a Decision Table that has an input parameter named ac:

In XML, this will be modeled as follows:

<DecisionTable name="cash decision table">
    <Columns>
        <Condition name="Concepto de Contabilidad" expression="ac == $value" />
        <Action name="Escribe un mensaje" expression="$value" type="Notice" />
    </Columns>
    <Data>
        <Row>
            <Value>Pagadero</Value>
            <Value>Concepto de cuentas es dinero en efectivo</Value>
        </Row>
    </Data>
</DecisionTable>

Concept

Values

In both rules, in the condition column:

  • Payable
  • Pagadero

are both referring to the same thing, but in different languages. In resolving the values on evaluation, all translations (e.g., English, Spanish, German, etc.) must be resolved against the same reference value. This reference value can be either a key (e.g., code, id, number, etc.) or a default main locale (e.g., in English, which is Payable).

Let’s say we are going to assume 1 is an identifier or code we want to be resolved for all the translations of Payable.

const string payableValue = "1";
var termsTranslation = new NameValueCollection
{
    {"Payable", payableValue},
    {"Pagadero", payableValue}
};

As you can see, at this stage all of the translations of the Payable term are registered against payableValue.

When the engine (i.e. RuntimeEngine) is created, simply use the Entries property and add these translations:

foreach (var term in termsTranslation.AllKeys)
    engine.Entries.Add(term, string.Format("'{0}'", termsTranslation[term]), false);

Messages

Notification is smart and will simply pick the correct message based on the current Thread’s UI Culture. Therefore, all of the messages must be registered for the target culture:

var msg1_spanish = new Dictionary<string, string> { { "MSG_1", "Concepto de cuentas es dinero en efectivo" } };
var msg1_english = new Dictionary<string, string> { { "MSG_1", "Account concept is cash" } };
engine.OnRunning = (e) =>
{
    e.ExecutorSetup.MultilingualMessages.Register(msg1_spanish, CultureInfo.GetCultureInfo("es-ES"));
    e.ExecutorSetup.MultilingualMessages.Register(msg1_english, CultureInfo.GetCultureInfo("en-US"));
};

Please note that in this example, MSG_1 is the key to different translations of the message corresponding to the business rule. Make sure in your decision table you set useMessageId, or if you use FlexRule Designer then you need to set it as an Action of the Decision Table:

Final Decision Table

In the final Decision Table, we do not add a direct message, instead, we will be using a message code or id. Now for the values, all the translations of the term (i.e., Payable) will be accepted.

Putting it all together

public void test_multilingual_value_and_message()
{
    const string dt = @"
    <DecisionTable name=""DT1"">
      <Declaration>
        <Define direction=""in"" name=""ac"" />
      </Declaration>
      <Columns>
        <Condition name=""Accounting Concept"" expression=""ac==$value"" />
        <Action name=""Write Message""  expression=""$value"" type=""notice"" notice=""information"" useMessageId=""true""/>
      </Columns>
      <Data>
        <Row>
          <Value>Payable</Value>
          <Value>MSG_1</Value>
        </Row>
      </Data>
    </DecisionTable>
    ";
 
    var engine = RuntimeEngine.FromXml(Encoding.UTF8.GetBytes(dt));
 
    // Register all terms' value translations
    const string payableValue = "1";
    var termsTranslation = new NameValueCollection
    {
        {"Payable", payableValue},
        {"Pagadero", payableValue}
    };
 
    foreach (var term in termsTranslation.AllKeys)
        engine.Entries.Add(term, string.Format("'{0}'", termsTranslation[term]), false);
 
 
    // Register notifications messages for different culture:
    // load messageId and translation for Spanish
    var msg1_spanish = new Dictionary<string, string> { { "MSG_1", "Concepto de cuentas es dinero en efectivo" } };
    engine.RegisterMultilingualMessage(msg1_spanish, CultureInfo.GetCultureInfo("es-ES"));
 
    // load messageId and translation for English
    var msg1_english = new Dictionary<string, string> { { "MSG_1", "Account concept is cash" } };
    engine.RegisterMultilingualMessage(msg1_english, CultureInfo.GetCultureInfo("en-US"));
 
 
    System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
    var result = engine.Run(termsTranslation["Payable"]);
    Assert.AreEqual(msg1_english["MSG_1"], result.Notifications.Default.Notices.First().Message);
    result = engine.Run(termsTranslation["Pagadero"]);
    Assert.AreEqual(msg1_english["MSG_1"], result.Notifications.Default.Notices.First().Message);
 
    System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("es-ES");
    result = engine.Run(termsTranslation["Pagadero"]);
    Assert.AreEqual(msg1_spanish["MSG_1"], result.Notifications.Default.Notices.First().Message);
    result = engine.Run(termsTranslation["Payable"]);
    Assert.AreEqual(msg1_spanish["MSG_1"], result.Notifications.Default.Notices.First().Message);
}

Business Glossary

A business glossary is a dictionary of the set of your business terminologies, definitions, synonyms, and translations. With the use of the business glossary, the rule integration, packaging, deployment, and integration of multilingual values are streamlined.

Sample

Let’s assume we have the following Decision Table and glossary defined and we load it as a ruleset:

private static IRuleSet GetAccountingRuleSet()
{
    var mc = new ModelContainer();
 
    const string dt = @"
    <DecisionTable name=""cash decision table"">
        <Declaration>
            <Define direction=""in"" name=""ac"" />
        </Declaration>
        <Glossary>
            <GlossarySource uri=""ruleset:///accounting terms""/>
        </Glossary>
        <Columns>
            <Condition name=""Accounting Concept"" term=""Payable"" />
        <Action name=""Write Message""  expression=""$value"" type=""notice"" notice=""information"" useMessageId=""true""/>
        </Columns>
        <Data>
            <Row>
                <Value>Payable</Value>
                <Value>MSG_1</Value>
            </Row>
        </Data>
    </DecisionTable>
    ";
    LoadAdapterUtility.FillNavigableSource(mc, Encoding.UTF8.GetBytes(dt));
 
    const string gl = @"
    <Glossary name=""accounting terms"">
        <Term name=""Payable"" expression=""ac==$value"">
            <Synonym value=""Pagadero""/>
        </Term>
    </Glossary>
    ";
    LoadAdapterUtility.FillNavigableSource(mc, Encoding.UTF8.GetBytes(gl));
 
    var rs = RuleSet.HierarchicalRuleSet();
    rs.AddModel("", mc);
    return rs;
}

The Decision Table is the same as we discussed earlier, but in addition, it has a reference to a defined glossary.

Preloaded glossary

When your application maintains a business glossary outside of the rule definition, this approach allows you to load the business glossary from any external data source and inject it during business rule load and execution.

var rs = GetAccountingRuleSet();
const string rsUri = "ruleset:///cash decision table";
 
// load glossary from ruleset
var glossary = Glossary.Load(rs.SelectFirst(rsUri), rs);
 
// create engine instance and provide the pre-loaded glossary
var engine = RuntimeEngine.FromRuleSet(rs, rsUri, glossary);
 
// Register all terms' value translations
engine.Entries.ImportFromGlossary(glossary);

Embedded glossary

In this approach, the engine loads the glossary from the ruleset that is referenced in the Decision Table automatically.

var rs = GetAccountingRuleSet();
const string rsUri = "ruleset:///cash decision table";
 
// create engine instance and let the embedded glossary load and be utilised
var engine = RuntimeEngine.FromRuleSet(rs, rsUri);
 
// Get the glossary from the loaded ruleset
var glossary = Glossary.Load(rs.SelectFirst(rsUri), rs);
// Register all terms' value translations
engine.Entries.ImportFromGlossary(glossary);

Multilingual using Business Glossary Sample

The example below shows how to use the business glossary to make values multilingual in a sample Decision Table:

[TestMethod]
public void test_multilingual_load_glossary_onload()
{
    var rs = GetAccountingRuleSet();
    const string rsUri = "ruleset:///cash decision table";
 
    var engine = RuntimeEngine.FromRuleSet(rs, rsUri);
 
    var glossary = Glossary.Load(rs.SelectFirst(rsUri), rs);
    // Register all terms' value translations
    engine.Entries.ImportFromGlossary(glossary);
 
    // Register notifications
    var msg1_spanish = new Dictionary<string, string> { { "MSG_1", "Concepto de cuentas es dinero en efectivo" } };
    engine.RegisterMultilingualMessage(msg1_spanish, CultureInfo.GetCultureInfo("es-ES"));
 
    var msg1_english = new Dictionary<string, string> { { "MSG_1", "Account concept is cash" } };
    engine.RegisterMultilingualMessage(msg1_english, CultureInfo.GetCultureInfo("en-US"));
 
 
    System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
    var result = engine.Run(glossary.Lookup("Payable"));
    Assert.AreEqual(msg1_english["MSG_1"], result.Notifications.Default.Notices.First().Message);
    result = engine.Run(glossary.Lookup("Pagadero"));
    Assert.AreEqual(msg1_english["MSG_1"], result.Notifications.Default.Notices.First().Message);
 
    System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("es-ES");
    result = engine.Run(glossary.Lookup("Pagadero"));
    Assert.AreEqual(msg1_spanish["MSG_1"], result.Notifications.Default.Notices.First().Message);
    result = engine.Run(glossary.Lookup("Payable"));
    Assert.AreEqual(msg1_spanish["MSG_1"], result.Notifications.Default.Notices.First().Message);
}

What’s next?

  1. Introduction to decision tables
  2. Preparing a decision table
  3. Modeling decision table
  4. Decision Model and Notation – decision table
  5. Check overlaps
  6. Decision Table final logic
  7. Multilingual decision table
  8. Decision Table 101

Tutorials

  1. Decision Table Hello World
Updated on April 4, 2022

Was this article helpful?