Localization of a .NET application, in particular ASP.NET WebForms

Hi Habradrug!
Of course, I understand that this topic is already hackneyed, but I will try to describe my vision of application localization and its implementation. And so, what we want to get as a result: A system for quickly localizing a .NET application, and in particular WebForms, at the post-development stage. On points:

1. Application development: minimum attention to localization.
2. Development completion: minimum time for localization.
And still it is necessary that all this was as convenient and simple as possible.

In short, everything will look like this:

We will have a method, something like gettext, into which we will pass some text, or a key + text, and the method, in turn, will return a localized string to us or what was transferred to it. And there will also be a utility that parses * .cs , * .aspx , * .ascx files in search of this very method, receives keys and text from there, looks or these texts and keys are already in the application resources. Then we will use google translate, which would at least be poor, but translate new texts. And finally, edit the translations and save it all back to resources. Done.
Obviously, a similar, or maybe even a better mechanism has already been developed, but climbing on the Internet, I saw only mega heavy and also paid solutions.



So, let's get down to implementation. Let's start with the helper class, a for server-side controls with ExpressionBuilder-a:

public static class L
{
  private static readonly Regex KeyRepairRegex = new Regex("[^\\w\\d]", RegexOptions.Compiled);
  
  public static string Run(string value)
  {
    var key = GenerateKey(value);
    var localized = Resources.ResourceManager.GetString(key);
    return string.IsNullOrEmpty(localized) ? value : localized;
  }

  public static string Run(string key, string value)
  {
    var localized = Resources.ResourceManager.GetString(key);
    return string.IsNullOrEmpty(localized) ? value : localized;
  }

  public static string Run(string key, string value, params object[] format)
  {
    var localized = Run(key, value);

    if (format != null && format.Length > 0)
    {
      try
      {
        localized = string.Format(localized, format);
      }
      catch
      {
        
      }
    }
    return localized;
  }

  private static string GenerateKey(string value)
  {
    if (string.IsNullOrEmpty(value)) return value;

    value = value.Length > 23 ? value.Substring(0, 23) + "__" : value;    
    value = KeyRepairRegex.Replace(value, "_");
    if (char.IsUpper(value[0])) value = "_" + value;
    value = value.ToLowerInvariant();

    return value;
  }
}


Now, during development, in places where we need localization, we write:

<%= L.Run("Hello, Mr.Everybody") %>


The L class is not specifically located in any namespace , so that it does not have to be imported in * .ascx files.

<%@ Import Namespace ="Example.Namespace" %>


Here, as you can see from the code, there is a quite important GenerateKey method . If we transmit only text, without a key, then this method will generate a key and we will look for this key in resources. The key in the resources can contain only the characters [a-z0-9_]. And our utility will use the same method to generate keys. Now let's look at ExpressionBuilder and give a more specific example of the whole scheme. Why do we need this very Builder?

The construction <% =%> in server controls will lead to an error, but for <% #%> you need to additionally call the DataBind method, which is not very convenient. We need a class inherited from ExpressionBuilder.

public class LExpressionBuilder: ExpressionBuilder
  {
    public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
    {
      var thisType = new CodeTypeReferenceExpression(GetType());      
      var expression = new CodePrimitiveExpression(entry.Expression.Trim());
      const string evaluationMethod = "Localize";
      return new CodeMethodInvokeExpression(thisType, evaluationMethod, new CodeExpression[] { expression });
    }

    public static string Localize(string expression)
    {
      return L.Run(expression);
    }

    public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
    {
      return Localize(entry.Expression);
    }

    public override bool SupportsEvaluate
    {
      get { return true; }
    }
  }


As a result, we write:



The first part is ready. We pass to the utility. This is an ordinary WinForms application.

Class Diagram

As you can see from the diagram, the code is pretty straightforward. But the main thing is simple. We load the Solution or project we need , specify the path to our resource file in the Settings class. And ... let's go! Solution class will load all files. CodeParser will load all the lines and keys into the Translation class , which in turn will load all translations from the resources and show all this in the GridView. Now you can translate it manually or via google, you can save the original lines in a text file or implement something else, for example, saving in xml .

Assembla subversion(Registration is optional)

This utility is written for myself in haste, on the principle: “if only it worked,” so I ask the guru not to judge strictly. If anyone is interested, I can bring this matter to at least the alpha version.

What else would you like to do:

CodeParser tearing out lines from files remembers their positions. And since most of the localized strings go to me initially without keys, it takes a few extra nanoseconds to generate them, so the utility should insert the keys to the strings in the files afterwards.

But even without this, everything works very smartly, although I will be glad to listen to any criticism or suggestions.

Thanks for attention.

* Source code was highlighted with Source Code Highlighter.

Also popular now: