Is this a good solution for localizing plug-in components?

I asked a question earlier, which had only one answer. I had time to play with it now and have a plan, but I want to get feedback if this is a good idea.

Problem:

I need a component that has a name (invariant, used to identify the component) so that its name is localized in the application that consumes it without polluting the component model with the DisplayName attribute. A component can exist in a separate dll and load dynamically at runtime.

I feel that the component’s DLL should be responsible for providing the localized name (this seems like good encapsulation), but the application using the component should be responsible for getting / using the localized name (the fact that the component has a different name for display purposes is not a problem for component, but for "presentation" using this component).

Decision:

Add the resource to the DLL components with the same name as the file in which the component class is located. Add a string to the resource with the key, which is the name of the component.

In the application, get the localized name like this:

ExternalObject obj = GetExternalObject (); ResourceManager manager = new ResourceManager (obj.GetType ()); string localisedName= manager.GetString (obj.Name); 

This code is likely to be encapsulated in the Localiser class, but passes the point. It seems to work, but is it a good idea, or is there a better / more standard way to do this?

EDIT: I have to point out that one thing I'm not sure about this solution about is that the resources should be in a .resx file that has the same name as the file the class is in. it works because the resource file can be identified from the type name. This is the same as localization for forms seems to work, and makes the visual studio put .resx as a “subcomponent” of the .cs file, which makes everything seem nice. But the visual studio then issues a warning (about changing a resource that is part of another project element) if I try to edit this file, which makes me think that there may be another way that I should do this.

+4
source share
3 answers

I think you have the right idea, but there is a better way to achieve this.

Presumably you have an interface that implements a plugin. Say IPluggable:

 interface IPluggable { ... string LocalizedName {get;} ... } 

From the main binary, load the plug-in assembly and create an IPluggable instance using reflection (I assume you have the GetExternalObject() method) and then access the localized name using the LocalizedName property. Inside the IPluggable implementation, create a ResourceManager and access the LocalizedName from the resx of this pluggable assembly.

What you do is a good encapsulation of the behavior in the plug-in assembly - it is responsible for providing you with a localized name, however it selects it without your male program, assuming that the ResourceManager can be created to access the localized name.

+1
source

I had a problem some time ago about localizing enum values, I'm not sure if it answers your question, but at least gives you a different approach that you need to keep in mind.

Start creating your own Localizing attribute

 /// <SUMMARY> /// Attribute used for localization. Description field should contain a reference to the Resource file for correct localization /// </SUMMARY> public class LocalizationAttribute : Attribute { public LocalizationAttribute(string description) { this._description = description; } private string _description; /// <SUMMARY> /// Used to reference a resource key /// </SUMMARY> public string Description { get { return this._description; } } } 

From there I create enum myself

 [TypeConverter(typeof(EnumToLocalizedString))] public enum ReviewReason { [LocalizationAttribute("ReviewReasonNewDocument")] NewDocument = 1, [LocalizationAttribute("ReviewReasonInternalAudit")] InternalAudit = 2, [LocalizationAttribute("ReviewReasonExternalAudit")] ExternalAudit = 3, [LocalizationAttribute("ReviewReasonChangedWorkBehaviour")] ChangedWorkBehaviour = 4, [LocalizationAttribute("ReviewReasonChangedWorkBehaviourBecauseOfComplaints")] ChangedWorkBehaviourBecauseOfComplaints = 5, [LocalizationAttribute("ReviewReasonMovedFromOlderSystem")] MovedFromOlderSystem = 6, [LocalizationAttribute("ReviewReasonPeriodicUpdate")] PeriodicUpdate = 7, [LocalizationAttribute("ReviewReasonDocumentChanged")] DocumentChanged = 8 } 

Then I created a type converter that will extract the LocalizationAttribute description key and access the resource file in order to get localization (the attribute description must match the resource key :))

 public class EnumToLocalizedString : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return (sourceType.Equals(typeof(Enum))); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return (destinationType.Equals(typeof(String))); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { return base.ConvertFrom(context, culture, value); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (!destinationType.Equals(typeof(String))) { throw new ArgumentException("Can only convert to string.", "destinationType"); } if (!value.GetType().BaseType.Equals(typeof(Enum))) { throw new ArgumentException("Can only convert an instance of enum.", "value"); } string name = value.ToString(); object[] attrs = value.GetType().GetField(name).GetCustomAttributes(typeof(LocalizationAttribute), false); if (attrs.Length != 1 !(attrs[0] is LocalizationAttribute)) { throw new ArgumentException("Invalid enum argument"); } return Handbok.Code.Resources.handbok.ResourceManager.GetString(((LocalizationAttribute)attrs[0]).Description); } } 

Finally, I created a client that uses TypeConverter, which in this case is a collection

 public class ReviewReasonCollection { private static Collection<KEYVALUEPAIR<REVIEWREASON,>> _reviewReasons; public static Collection<KEYVALUEPAIR<REVIEWREASON,>> AllReviewReasons { get { if (_reviewReasons == null) { _reviewReasons = new Collection<KEYVALUEPAIR<REVIEWREASON,>>(); TypeConverter t = TypeDescriptor.GetConverter(typeof(ReviewReason)); foreach (ReviewReason reviewReason in Enum.GetValues(typeof(ReviewReason))) { _reviewReasons.Add(new KeyValuePair<REVIEWREASON,>(reviewReason, t.ConvertToString(reviewReason))); } } return _reviewReasons; } } } 

I originally posted this solution on my blog . Hope this helps you :)

0
source

The problem with what you suggested is that it will be difficult to update translations, and even a programmer may be required. Also, how are you going to update translations without updating all applications?

I have done a lot from the translated applications, and what I did has a separate text file with translations, created something like this:

[English]
Done = Done

[Norwegian]
Done = Ferdig

And I have a function called TranslateForm () that I call inside the Form Show event, which will translate all the user interface elements. The TranslateForm () function will have things like

 buttonDone.Text = Translate.GetTranslation("Done"); 

The last part with TranslateForm is not an optimal solution, I think that over time I will move on to a solution in which the control itself calls the Translate class. The advantage of using this system is that its a simple program for the programmer, you can have other ppl add translations, without having to do manual work after that (this is important for me, since I have community-based translations), so they are updated often and I do not want to waste time on this. I can also update translations while the application is running, without having to restart or update the application.

-one
source

All Articles