New support for translating attributes
Version 4.65.0069 contains new support for translating attributes.
Over the years, I have repeatedly been asked about how to translate attributes. Up to now, the Add-In has not supported the translation of attributes, because
- attributes provide meta data, which does not usually require translation
- I simply didn't have a good strategy to translate them.
I still think that attributes usually do not require translation, but I now accept that there are legitimate cases where it does make sense. In particular, if you are using the PropertyGrid control, the user interface shows texts which are defined as attributes.
However, you cannot simply replace the text in an attribute definition with a function call. The question is, how can you localize an attribute definition?
Here I must give credit to Patrick Ribault for proposing the following simple method.
Suppose we have a class defined as follows:
class Person
{
private string firstName = "";
private string lastName = "";
[Category ( "Personal Data" )]
[DisplayName ( "First Name" )]
[Description ( "Enter your first name in this field" )]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[Category ( "Personal Data" )]
[DisplayName ( "Last Name" )]
[Description ( "Enter your last name in this field" )]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
Public Class Person
Dim mFirstName as String
Dim mLastName as String
<Category("Personal Data")> _
<DisplayName("First Name")> _
<Description("Enter your first name in this field")> _
Public Property FirstName As String
Get
Return mFirstName
End Get
Set(ByVal value As String)
mFirstName = value
End Set
End Property
<Category("Personal Data")> _
<DisplayName("Last Name")> _
<Description("Enter your last name in this field")> _
Public Property LastName As String
Get
Return mLastName
End Get
Set(ByVal value As String)
mLastName = value
End Set
End Property
End Class
The trick is, to derive new attribute classes, with an additional StringID parameter. For example, for the Category attribute:
public class ml_CategoryAttribute : System.ComponentModel.CategoryAttribute
{
public ml_CategoryAttribute( int StringID, string Text )
: base ( ml.ml_string ( StringID, Text ) )
{
}
}
Friend Class ml_CategoryAttribute
Inherits System.ComponentModel.CategoryAttribute
Sub New ( ByVal StringID As Integer, ByVal text As String )
MyBase.New ( ml_string ( StringID, text ) )
End Sub
End Class
The new attribute class is derived from the original class, and provides a new constructor with an additional StringID parameter. The new constructor fetches the localized string using the ml_string() function, and then calls the constructor in the base class.
The localized class definition then looks like this:
class Person
{
private string firstName = "";
private string lastName = "";
[ml_Category ( 2,"Personal Data" )]
[ml_DisplayName ( 3,"First Name" )]
[ml_Description ( 4,"Enter your first name in this field" )]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[ml_Category ( 2,"Personal Data" )]
[ml_DisplayName ( 5,"Last Name" )]
[ml_Description ( 6,"Enter your last name in this field" )]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
Public Class Person
Dim mFirstName as String
Dim mLastName as String
<ml_Category(4,"Personal Data")> _
<ml_DisplayName(6,"First Name")> _
<ml_Description(5,"Enter your first name in this field")> _
Public Property FirstName As String
Get
Return mFirstName
End Get
Set(ByVal value As String)
mFirstName = value
End Set
End Property
<ml_Category(4,"Personal Data")> _
<ml_DisplayName(3,"Last Name")> _
<ml_Description(2,"Enter your last name in this field")> _
Public Property LastName As String
Get
Return mLastName
End Get
Set(ByVal value As String)
mLastName = value
End Set
End Property
End Class
As you can see, the original attribute definition, for example Category("Personal Data"), has been replaced with the derived attribute, for example ml_Category(4,"Personal Data").
The following screenshots show, that it really does work.
|
|
That is basically the complete mechanism, and I hope you will agree that it is technically a simple and an elegant solution.
For simple cases, such as the class shown above, the Add-In makes all necessary changes:
- it detects that the string is part of an attribute definition, which is indicates with a letter A in the flag column (see below)
- it generates the derived class, which it places in the mlstring module (MlString.cs or MlString.vb)
- it prefixes the attribute name with ml_
- it inserts the StringID parameter
The following screenshot shows how the Attributes are indicated in the flag column.
Finally, there are one or two limitations.
If the attribute class is Sealed (NotInheritable in VB), then it is impossible to create a derived class. The Add-In cannot localize these attributes. This applies to quite a few attributes, in particular the Assembly attributes defined in Assembly.cs/.vb. On the other hand, it doesn't seem to apply to any of the attributes which you would actually want to localize.
If the Add-In detects a Sealed attribute class, then it will not show the attribute in the grid. This might not work in all circumstances, so there is still a chance that the Add-In will display an attribute which cannot be localized.
Whilst that was a fundamental limitation, the remaining limitations are just due to my implementation:
- The Add-In does not generate a correct derived class if the attribute has more than one parameter. You would have to generate this class yourself.
- The definition of an attribute should not be split over multiple lines. If it is, then the Add-In will probably not recognize that it is an attribute.
Phil