Revit Add-in with Multiple Language Forms based on Current UI Culture

GJennings-BM
Contributor
Contributor

Revit Add-in with Multiple Language Forms based on Current UI Culture

GJennings-BM
Contributor
Contributor

Hello community,  

I am struggling to get my C# code correct to convert my user forms into each culture.   I have tried for days, but I cannot get my labels to change languages when opening Revit in different language versions.  I can get my Ribbon Panel button to change languages using a RibbonResources.resx file in my App.cs, but my FormExport.resx files are not providing language translation values.  

 

public FormExport(Autodesk.Revit.DB.Document doc) 
{

CultureInfo cultureName = new CultureInfo(Thread.CurrentThread.CurrentUICulture.Name);
string cultureRef = cultureName.Name;

Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureRef);

Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureRef);

 

InitializeComponent();
familyDocument = doc;

 

// Get the localized label text from the resource .resx file  
string localizedLabelText = GetLocalizedTextFromResource("lblFamilyName.Text");
string localizedHeaderText = GetLocalizedTextFromResource("lblHeader.Text");

 

// Set the label text with the localized value
lblFamilyName.Text = localizedLabelText;
lblHeader.Text = localizedHeaderText;

 

}

 

I have this function set below my form code :

private string GetLocalizedTextFromResource(string key)
{
try
{
// Load the appropriate resource file based on the user's selected language
ResourceManager resourceManager = new ResourceManager("MyApp.FormExport", typeof(FormExport).Assembly);
CultureInfo currentCulture = Thread.CurrentThread.CurrentUICulture;

// Fetch the localized text for the given key from the resource
string localizedText = resourceManager.GetString(key, currentCulture);

// If the resource for the given key is not found in the selected culture,
// explicitly load the default resource (English) using CultureInfo.InvariantCulture
if (localizedText == null)
{
localizedText = resourceManager.GetString(key, CultureInfo.InvariantCulture);
}

// If the resource is still not found, return the key itself as a fallback
return localizedText ?? key;
}
catch (MissingManifestResourceException ex)
{
// Log the exception
Console.WriteLine($"Resource file not found. Exception: {ex.Message}");
return key; // Return the key itself as a fallback
}
}

 

My form .resx files are:

FormExport.resx

FormExport.en.resx

FormExport.fr.resx

.....(continued)

 

Thank you for any assistance you can provide.  

Geoff

0 Likes
Reply
Accepted solutions (2)
1,124 Views
9 Replies
Replies (9)

jeremy_tammik
Autodesk
Autodesk

You don't say what research you have performed to optimise your setup. Searching around a bit for things like resx and .NET leads me to lots of interesting recommendations like this one:

    

https://stackoverflow.com/questions/373388/best-way-to-implement-multi-language-globalization-in-lar...

   

It includes so much advice that it is impossible to guess what you may or may not already be adhering to...

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

GJennings-BM
Contributor
Contributor

Good evening and thank you Jeremy for your response.

 

I am testing options mentioned in the link you sent.  Below is just a small list sites I have researched and tested against.  It has become very confusing since everyone seems to have a different method.   I am simply building a C# .Net app for Revit under the Add-in tab.  I have five WinForms.  One is a primary WinForms and the other WinForms support secondary operations.  This is all been built in Visual Studio 2022 for Revit 2021-2024.  The app has been submitted to the app store for publishing.  Now, I just need to prepare the WinForms for a larger international base of users.   

 

It should be extremely simple.  Again, I have my ribbon panel working for any language, but the translations of my WinForms are my challenge.  Beyond the sites listed below, I have spent hours reviewing YouTube videos, ChatGPT, reading manuals, and website blogs.  In visual studio, I have done the process of setting each form to 'Localizable' = true, then Language = "France, Spanish,...", customized the forms for .resx files.  I have tested with separate manual .resx files, and tons of programming methods.  Frustrating for something that is effectively a basic task.  

 

I am hoping someone can provide c# .net guidance showing the code requirements of a single form that references the appropriate .resx based upon the user's Revit language version.   I currently have a 'Forms" folder for all of my WinForms and their associated .resx language files.  

 

Create the Multilingual .bundle file

 
All Language Revit Versions
 
Language Tags
 
Get Revit Language
 
Family Content Localization
 
Another Resource for Creating Resx files and Revit UI Culture
 
Label Control for BuiltIn Parameter Languages
 
Localize Ribbon

 

Thank you. Geoff

 
0 Likes

ricaun
Advisor
Advisor
Accepted solution

I remember in another post that someone has a similar problem with Form and multi-language.

 

The .resx file should swap automatically when depending on the CultureInfo, if the resource is now changed automatically probably your key language is different from the CultureInfo in the application, like en is different from en-US and fr is different from fr-FR.

 

Just update your resource file name to match the same CultureInfo that Revit uses.

 

Here is a table with all the Langueges and keys: https://github.com/ricaun/RevitAddin.ResourcesExample

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

GJennings-BM
Contributor
Contributor
Accepted solution

Thank you for your response and excellent quality video of your .resx management.   Your methods will be useful on some other apps I plan to create.  

 

I was finally successful at getting my C# Winforms to show in any UICulture languages!  I want to share this information with others:  

 

RIBBON PANEL LOCALIZATION: 

I added a language switcher file based on Andrey Bushman' sample file.  This has been modified to 'RVTLanguages.cs'.  The file was placed in my root folder below my C# project name.

In the same location, I also created three .resx files:  

RibbonResources.resx  (empty and set to 'Internal')

RibbonResources.en-US.resx (all of my panel data - see image below)

RibbonResources.fr-FR.resx (my french translations)  

To keep my panel button narrow and allow for wrapping of text, I created two lines in the .resx file

Ribbon_EN_resx file.png

 

In my App.cs :

public Result OnStartup(UIControlledApplication application)
{
 #region READ AND SET THE LANGUAGE ENVIRONMENT USING THE RVTLanguages.cs file
 RVTLanguages.Cultures(application.ControlledApplication.Language);
 ResourceManager res_mng = new ResourceManager(typeof(RibbonResources));
 #endregion

RibbonPanel p = application.CreateRibbonPanel(RibbonResources.ResourceManager.GetString("PanelName"));  
               
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
string buttonText1 = RibbonResources.ResourceManager.GetString("CommandName1");
string buttonText2 = RibbonResources.ResourceManager.GetString("CommandName2");

PushButtonData btnMyApp = new PushButtonData("btnMyApp", buttonText1 + "\n" + buttonText2, thisAssemblyPath, typeof(Command).FullName) 
{
  LargeImage = imgSrc,
  Image = imgSrcTB,
  LongDescription = RibbonResources.ResourceManager.GetString("CommandDescription"),   
  ToolTip = RibbonResources.ResourceManager.GetString("CommandToolTip") 
};

// set help file to reference .bundle file help.html
string helpFolder = Path.Combine(parentFolder, "help.html");
ContextualHelp contextHelp = new ContextualHelp(
ContextualHelpType.ChmFile, helpFolder); 
btnMyApp.SetContextualHelp(contextHelp);

p.AddItem(btnMyApp);

 

This was all that was required for setting up a localized ribbon button. 

Ribbon_EN_and_FR.png

 

CREATING THE LOCALIZED WINFORMS:  

I created a folder called "Forms" and then placed all of my WinForms under this folder.  

After my forms were created, I then manually created two new .resx files.

for example:  "MyForm.en-US.resx"  and "MyForm.fr-FR.resx"

These files were placed in the Forms folder along with my main WinForm.  

 

In my Winform code  I did the following:   

MyForm.cs

public sealed class UICultureSwitcher : IDisposable
{
    CultureInfo previous;
    public UICultureSwitcher()
    {
        CultureInfo culture = new CultureInfo(Thread
        .CurrentThread.CurrentCulture.Name);

        previous = Thread.CurrentThread.CurrentUICulture;
        Thread.CurrentThread.CurrentUICulture = culture;
     }
     void IDisposable.Dispose()
     {
         Thread.CurrentThread.CurrentUICulture = previous;
     }
}

public MyForm(Autodesk.Revit.DB.Document doc)  
{
      ResourceManager res_mng = new ResourceManager(typeof(MyForm));
      ResourceSet resourceSet = res_mng.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true);

      InitializeComponent();
      familyDocument = doc;

      label1.Text = GetLocalizedTextFromResource("label1Text"); 
      label2.Text = GetLocalizedTextFromResource("label2.Text"); 
      label3.Text = GetLocalizedTextFromResource("label3.Text");
      label4.Text = GetLocalizedTextFromResource("label4.Text");
      ............
      <rest of code>
}

 

Below I have a function that will collect the necessary language information in the .resx file.

private string GetLocalizedTextFromResource(string key)
{
    try
    {
     // Load the appropriate resource file based on the user's selected language
     ResourceManager resourceManager = new ResourceManager("MyApp.Forms.MyForm", typeof(MyForm).Assembly);
                CultureInfo currentCulture = Thread.CurrentThread.CurrentUICulture;

     // Fetch the localized text for the given key from the resource
     string localizedText = resourceManager.GetString(key, currentCulture);

     // If the resource for the given key is not found in the culture,
     // explicitly load the default resource (English) using CultureInfo.InvariantCulture
     if (localizedText == null)
        {
        localizedText = resourceManager.GetString(key, CultureInfo.InvariantCulture);
        }

       // If the resource is still not found, return the key itself as a fallback
       return localizedText ?? key;
  }
  catch (MissingManifestResourceException ex)
  {
       // Handle the exception if the resource file is not found
       // Log the exception
       Console.WriteLine($"Resource file not found. Exception: {ex.Message}");
       return key; // Return the key itself as a fallback
   }
}

 

I then repeat the same methods for my other forms.  I have included screenshots of the end results.  Now, I am updating the datagrid based upon a similar workflow.  

 

I truly hope this helps others.  This has been a very confusing journey.  Everything mentioned was done in Visual Studio 2022 and for Revit 2022-2024.  

 

Kind regards,

Geoff Jennings

0 Likes

ricaun
Advisor
Advisor

Neat!

 

I believe setting the Thread.CurrentThread.CurrentUICulture and Thread.CurrentThread.CurrentCulture is not necessary to make multilanguage works, Revit already starts with the CultureInfo correctly, the only reason to change should be to test if your Form is working in another CurrentUICulture language. Using your class UICultureSwitcher does the trick.

 

And why are you using CommandName1 and CommandName2 to create the second line, only one CommandName with multiple lines should work, like your CommandDescription that has multiple lines.

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

GJennings-BM
Contributor
Contributor

You are correct about the CommandName1 and CommandName2.  I originally created the two strings before I did the multiline commandDescription.  Previously, I had tried the "\r\n" method in the .resx file and it was making it part of a single full string versus giving me two lines of strings.  

I change the .resx files for my Ribbon code and generated the Spanish version.  The single CommandName with a shift+return allows for the multiple lines in the ribbon button.  

Ribbon_ES.png

 

As for the language switcher, I have plans for expanding functionality for an upcoming Pro version of the app. 

Thanks again for your video and quick response.  

jeremy_tammik
Autodesk
Autodesk

Thank you, Geoff, for your travail and sorting out the confusion, and Luiz for your great support!

  

Edited and summarised for posterity:

  

https://thebuildingcoder.typepad.com/blog/2023/08/resx-language-management-and-aps-devcons.html

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

MattKincaid
Advocate
Advocate

@GJennings-BM  Many thanks for the helpful post!  I implemented your ribbon methodology in my Add-in and it's working quite well.

Curious if you have any recommendations for handling unsupported languages?  For example, the user can set Revit to French (FRA), but at the moment my addin doesn't have a resource file for that language.  So RibbonResources.ResourceManager.GetString, just returns null until you switch to a supported language.  Is there a good way to make it fail over to an existing resource file?

0 Likes

GJennings-BM
Contributor
Contributor

Hello Matt,

 

You can set up an try-catch clause in your code. This way, if an .resx file is not found, it will default to a preferred language.  In the below example, I setup the failsafe to fr-FR.  
=====

public sealed class UICultureSwitcher : IDisposable
{
CultureInfo previous;

public UICultureSwitcher(string cultureName)
{
CultureInfo culture = new CultureInfo(cultureName);

previous = Thread.CurrentThread.CurrentUICulture;

try
{
// Attempt to load the resource file for the specified culture.
ResourceManager rm = new ResourceManager("YourAppNamespace.Resources.Labels", typeof(UICultureSwitcher).Assembly);
rm.GetString("SomeKey", culture); // Use a key from your resource file.
Thread.CurrentThread.CurrentUICulture = culture;
}
catch (MissingManifestResourceException)
{
// If the resource file for the specified culture is not found, fall back to fr-FR.
Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");
}
}

void IDisposable.Dispose()
{
Thread.CurrentThread.CurrentUICulture = previous;
}
}

=====
I hope this helps. 

Geoff