Dynamic localization of Actipro strings with .resx files

WPF Studio, Themes, and Shared Library for WPF Forum

The latest build of this product (v25.1.2) was released 1 month ago, which was before this thread was created.
Posted 1 month ago by BenjaminLopVic - France
Version: 25.1.1
Avatar

Hello,

In our app, we need to handle multiple languages, so we use the .resx files to automaticly localize our strings.

Is it possible to achieve this with Actipro strings?

I know we can redefine strings like this : 

ActiproSoftware.Products.Shared.SR.SetCustomString(ActiproSoftware.Products.Shared.SRName.UICommandCloseWindowText.ToString(), "Fermer");
ActiproSoftware.Products.Shared.SR.SetCustomString(ActiproSoftware.Products.Shared.SRName.UICommandMaximizeWindowText.ToString(), "Maximiser");
ActiproSoftware.Products.Shared.SR.SetCustomString(ActiproSoftware.Products.Shared.SRName.UICommandMinimizeWindowText.ToString(), "Minimiser");

But this is static — if the language changes, the strings won’t update automatically.

Thanks.

Comments (8)

Posted 1 month ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

Behind the scenes, all our string resources are also defined using standard Visual Studio string resources, which create .resx files.  Our SR classes do have the SetCustomString method, which creates an override in memory for the string resource value.  When we go to retrieve strings, we use the custom override if it is provided.  Otherwise our default string resource from the .resx is used.

Some static dependency properties in UI controls require string resources set for default values.  Since they are static, those defaults can't be updated unless you recreate the control or update the property values.  In other cases, string resources are used in the XAML templates for various UI controls.  Those also can only be updated when a new template is applied, such as when the related control is created.

In summary, if you are updating the language, it is probably best to restart your application or at least recreate the UI controls so that the updated values have a chance to take effect.


Actipro Software Support

Posted 1 month ago by BenjaminLopVic - France
Avatar

Thanks!
Is there any way to register multiple values for the same string (for example, one per language) and have Actipro automatically pick the right one based on the current culture?
That way, we wouldn’t need to manually call SetCustomString each time the language changes.

Posted 1 month ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

I'm sorry but SetCustomString, if used, is a simple override for the resource dictionary values of the strings and doesn't have anything to do with culture.

The good news is that our libraries are built using the standard .resx string resources mechanism in Visual Studio.  This allows you to create satellite assemblies with string resources for other cultures if you wish.  Our string resource names are in the SRName enumeration values in each assembly.

If you search the internet for "C# how to create culture-specific string resource satellite assemblies for an external library", you can find a lot of resources on the process of doing that sort of thing.  That is a standard .NET mechanism and would accomplish what you are looking to do, without having to use SetCustomString.


Actipro Software Support

Posted 1 month ago by BenjaminLopVic - France
Avatar

It would be great to use .resx files to localize Actipro strings.
I’m already using .resx files to localize the rest of my application, but I haven’t been able to make it work for Actipro elements.

I couldn’t find anything online about localizing external libraries, and I tried several tests with .resx files like ActiproSoftware.Docking.Wpf.resx, using the correct string names found in the SRName enums.

Would it be possible for you to provide a small sample showing how to create and configure satellite assemblies for Actipro? That would really help.
Thanks!

Answer - Posted 1 month ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

I did a Google search on the steps to build resources for an external third-party assembly and followed its AI results.  Effectively I did these things and it worked for me:

  • Created a new "ResourceSatelliteAssembly" C# library project.
  • Added a resources file named "ActiproSoftware.Products.Shared.Resources.fr.resx" to its root.
  • Added an entry named "UICloseButtonToolTip" with value "Fermer" to the .resx file with the VS string resource editor.
  • Made a batch file in the project root folder with these lines and executed it:
resgen ActiproSoftware.Products.Shared.Resources.fr.resx
al /t:lib /embed:ActiproSoftware.Products.Shared.Resources.fr.resources /culture:fr /out:ActiproSoftware.Shared.Wpf.resources.dll
  • Copied the "ActiproSoftware.Shared.Wpf.resources.dll" assembly that was generated to my app's "bin/Debug/fr" folder.
  • Inserted this line at the beginning of my app's startup logic to force French (for testing only):
Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");

After doing that, my app's main Window with WindowChrome applied had the French translation of "Close" as its tooltip.

If you'd like the exact source code for the project, you can write our support address to request it, as we can't attach files in these forums.

I hope that helps!


Actipro Software Support

Posted 15 days ago by BenjaminLopVic - France
Avatar

Hello,

I downloaded the source code you provided and followed your instructions precisely, but I'm still getting the same result: the Actipro label remains in English.

To avoid any interference, I created a brand-new WPF project with its own resources to verify the application's current language. I then added the generated "ActiproSoftware.Shared.Wpf.resources.dll" produced by your procedure into the "bin/Debug/fr" folder. However, the Close tooltip still displays "Close" instead of "Fermer".

Here is the link to my sample:
https://github.com/BBJ-Artecomm/ActiproLocalization

Thanks!

Posted 14 days ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

This baffled us for a while since when simply dropping the satellite assembly into a "fr" subfolder of our sample application, it worked perfectly.  After looking at your application, we see you are on .NET Framework 4.8, whereas our tests were on .NET 8 apps.  

I tried converting our satellite assembly to .NET 4.6.2 to match our base library used in your sample, but it was ignored.

When digging into differences between .NET Fx and more modern .NET with satellite resource assemblies, the web says that if the base assembly is strong-named, the satellite must also be strong named with the same key.  .NET Framework enforces that but .NET Core does not.  I believe that is the issue here, and is a poor design on Microsoft's part for this specific scenario.  Unfortunately for security reasons we cannot hand out the strong name key of our assemblies.  This scenario must be why they loosened things up in .NET Core.

Per these findings, until you move to .NET from .NET Framework, I don't believe you can use satellite assemblies for resources and will have to fall back on the SetCustomString mechanism.


Actipro Software Support

Answer - Posted 13 days ago by BenjaminLopVic - France
Avatar

Thanks for your research.

Unfortunately, since we are building a VSTO Add-In, we are stuck on .NET Framework and cannot move to .NET.

I ended up still creating the localized resources with the same keys, but at application startup we have to run a method that reads all the resources and sets the corresponding custom strings.

If anybody is interested, here is the method : 

public static void Translate()
{
    var resourceProperties = typeof(Properties.Resources)
                .GetProperties(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
                .Where(p => p.PropertyType == typeof(string));
    var srNames = Enum.GetNames(typeof(SRName));
    foreach (var prop in resourceProperties)
    {
        string key = prop.Name;
        if (srNames.Contains(key))
        {
            string value = (string)prop.GetValue(null);
            SR.SetCustomString(key, value);
        }
    }
}

Add Comment

Please log in to a validated account to post comments.