-
Notifications
You must be signed in to change notification settings - Fork 0
Localization
Localization is the process of translating resources of an application in a specific language. There can be two types of localization: localizing static resources and localizing dynamic resources. Both types of localization are out-of-the-box features of CodeModeler, and this topic emphasizes how a developer can leverage those features to streamline the localization process of his application.
Static resources refer to all those UI messages that will never change throughout your application's lifetime: labels, error messages, information messages, titles, button texts, tool tips, etc. CodeModeler handles them through the message concept. In the model, you can add messages which will create your localization message:
Each message is related to a culture in the .NET sense. By specifying the name attribute as shown in the example above, this will generate a property of the specified name in the localization manager. If the name attribute is not specified, no properties will be created; however, you'll be able to retrieve that message through the GetString or the GetStringWithDefault methods of the Manager class in the generated CodeModeler Runtime File (_cm_rt.cs in C#) file. The GetString method has several signatures, containing or not the culture in which to retrieve the string, but if no culture is specified, the culture of the current thread is used. Here is an example of the generated output:
public static string WelcomeMessage
{
get
{
return Manager.GetString("WelcomeMessage", "WelcomeMessage");
}
}
public static string GetString(string name, object[] args)
{
return Manager.GetString(null, name, args);
}
public static string GetString(string name, string defaultValue)
{
return Manager.GetString(null, name, null);
}
public static string GetString(System.Globalization.CultureInfo culture, string name, object[] args)
{
Manager loader = Manager.GetLoader();
string str = loader._resources.GetString(name, culture);
if ((str == null))
{
return name;
}
if ((args != null))
{
return string.Format(culture, str, args);
}
else
{
return str;
}
}
Validation messages are global messages, messages declared at the project level. They can be overridden if necessary, by declaring them explicitly in your model. Those messages are contained in the CMV namespace, the name of your validator, and the validation rule (CMV.[ValidatorName].[ValidationRule]). For instance, overriding the validation message used on a string MaxLength validation would be done as so:
By doing as shown here-before, all MaxLength validation messages raised by the string validator will use the specified message.
In order to modify the validation for a single property, you should declare it in the rule body such as:
In the example above we are overriding the error message for the MaxLength failure code. Available failure codes are defined in a FailureCode enumeration in the corresponding Validator class. For instance:
At runtime, the validator used for the example above is the StringValidator class, defined in the CodeModeler.Runtime.Rules namespace.
Ergo, the list of available failure code is defined in the FailureCode enumeration in the StringValidator class.
This enumeration contains the following values: Null, Empty, MinLength, MaxLength, InvalidCharacters. Hence, a default message is defined per failure code, the message name being the corresponding failure code.
Note: Available failure codes are now listed on the documentation page of the corresponding validation rule (see the Validation Rules section).
Dynamic resources refer to localized, runtime-created such as user-created, data. For instance, a web shop could have an international catalog in several languages, and this catalog should be displayed in a specific language depending on the users' language (i.e. culture in .Net). Administrators add, remove or modify items from this catalog during the application's lifetime: it cannot be hard coded in the source code. Thus, administrators of the catalog will need to create catalog items with labels in several languages in order to support internationalization. However, they shouldn't duplicate that catalog per culture as this would involve expensive and time-consuming management.
CodeModeler supports dynamic resources management as an out of the box feature, and it's supported through a CodeModeler Aspect. Several other patterns are also shipped with CodeModeler and all of them are contained in the Patterns folder of your CodeModeler installation directory.
This pattern modifies the generated output so that instead of creating a single table per entity it creates two tables per localized entity: on the one hand it creates an entity table containing all non-localized data except default values; and on the other hand, it creates a corresponding [EntityName]Localized table.
In the localized entity table, each row contains the id of the item, its culture, and the localized properties for this culture. This way when an item is loaded from the user interface (UI), the culture is passed to the data layer, and the localized data corresponding to the culture gets loaded from the [EntityName]Localized table or the entity table if no localized data were found. For more information about architectural concepts regarding dynamic resources management please check the localization topic in the architect guide.
To use the localization pattern, you need to import it in your model, and then to specify which properties to localize. To specify which properties to localize you need to add the localization namespace to the model, and add the localization:localizable attribute to the property to localize.
The localization pattern uses views in the data layer, and the SQL Server Producer doesn't generate views by default. Consequently, you need to explicitly configure it to produce views through the produceViews attribute. For more information on why views and how they are used please refer to the Architect Guide.
At generation time (and more precisely at the Methods step of the generation process), the pattern will be run and will modify the generated output, so that loading an instance of the Product entity will be done according to the current UI culture of the application since the Product entity contains localized data. Here is the generated method:
public static Sample.Product Load(int id)
{
if ((id == -1))
{
return null;
}
Sample.Product product = new Sample.Product();
CodeModeler.Runtime.CodeModelerPersistence persistence = CodeModelerContext.Get(Sample.Constants.SampleStoreName).Persistence;
persistence.CreateStoredProcedureCommand("Product_Load");
persistence.AddParameter("@Id", id, ((int)(-1)));
persistence.AddParameter("@Lcid", System.Threading.Thread.CurrentThread.CurrentUICulture.LCID, ((int)(1033)));
System.Data.IDataReader reader = null;
try
{
reader = persistence.ExecuteReader();
if ((reader.Read() == true))
{
product.ReadRecord(reader, CodeModeler.Runtime.ReloadOptions.Default);
product.EntityState = CodeModeler.Runtime.EntityState.Unchanged;
return product;
}
}
finally
{
if ((reader != null))
{
reader.Dispose();
}
persistence.CompleteCommand();
}
return null;
}
As you can see the localized value loaded depends on the current UI culture of the thread, so no extra code is needed on the Business Object Model consumer side: localized value matching the UI culture of the application get automatically loaded.
static void Main(string[] args)
{
int id = CreateProduct();
// Here I'm a client with an english UI
Thread.CurrentThread.CurrentUICulture = new CultureInfo(1033);
var product = Product.Load(id);
Console.WriteLine("[English UI] Product.Description: " + product.Description);
Console.WriteLine();
// Here I'm a client with a french UI
Thread.CurrentThread.CurrentUICulture = new CultureInfo(1036);
product = Product.Load(id);
Console.WriteLine("[French UI] Product.Description: " + product.Description);
Console.WriteLine();
Console.ReadKey();
return;
}
private static int CreateProduct()
{
var product = new Product();
Console.WriteLine("Creating a product localized in english...");
// First we create a product with an english description
Thread.CurrentThread.CurrentUICulture = new CultureInfo(1033);
product.Description = "A description in english";
product.Save();
Console.WriteLine("Done.");
Console.WriteLine("Adding a product description localized in french...");
// We create a french description.
Thread.CurrentThread.CurrentUICulture = new CultureInfo(1036);
product.Description = "Une description en francais";
product.Save();
Console.WriteLine("Done.");
Console.WriteLine();
return product.Id;
}
Here's the generated output:
Creating a product localized in english...
Done.
Adding a product description localized in french...
Done.
[English UI] Product.Description: A description in english
[French UI] Product.Description: Une description en francais
- Introduction
- Architect Guide
- Concepts
- Using Visual Studio
- Overview
- Creating a CodeModeler Project
- Visual Environment
- Project Hierarchy
- Design Surface
- Customizing Design Surfaces
- Ribbon Bar
- Property Grid
- Member Format Expressions
- Model Grid
- Method Editor
- View Editor
- Instance Editor and Grid
- Resources Editor
- Inferred Model Viewer
- Building
- Project Physical Layout
- Source Control Support
- Generating
- Aspect Oriented Design (AOD)
- Developer Guide
- The Business Object Model (BOM)
- CodeModeler Query Language (CMQL)
- Starting Guide - Tutorial
- Upgrade From CFE