vb.netmultilingualresourcemanager

Organizing Resource Files


I need to add support for alternate languages to my VB application. I have started by creating a resource file with strings and values for one form, using GetString. Before I go too far, I want to figure out the best organization. In VB6 I could have a string table with multiple languages in a file which made it easy to add more. It looks like I need files for each langue in .NET.

Should there be a single large file for each language for the entire project, or multiple files, say for groups of forms?

Here is a function I am using to access the strings:

    Public Function GetString(ByVal strValue As String)

    Select Case m_str_System_Language
        Case "EN" 'English
            rm = My.Resources.English.ResourceManager
        Case "FR" 'French
            'rm = My.Resources.French.ResourceManager
        Case "ES" 'Spanish
            'rm = My.Resources.Spanish.ResourceManager
        Case "DE" 'German
            'rm = My.Resources.German.ResourceManager
        Case Else '"EN" 'English
            rm = My.Resources.English.ResourceManager
    End Select

    'Return language specific string
    GetString = rm.GetString(strValue)

End Function

Solution

  • Well, this is a big field. For the beginning, read Walkthrough: Localizing Windows Forms. You will learn a lot from it. Sometimes you find some gems even on the Microsoft website. ;-)

    I use three strategies to localize apps. The first is:

    Localize Forms

    Thats quite easy.

    Localized ComboBox entries

    Sometimes you have ComboBoxes in which all texts must be localized. Then I use the old database table approach. This would look like:

    ID  Group        ItemID   en_US    de_DE
    1   Sexes        0        male     männlich
    2   Sexes        1        female   weiblich
    3   Sexes        2        diverse  divers
    4   FamilyStatus 0        single   ledig
    5   FamilyStatus 1        married  verheiratet
    6   FamilyStatus 2        divorced geschieden
    7   FamilyStatus 3        widowed  verwitwet
    

    And then get the right SELECT Statement depending on the language you want and the group you want.

    The other stuff

    The other stuff means e.g. MessageBox messages. That's stuff which is just shown temporaily or changes from time to time.

    Here the resources table in project properties is your default language store. To add new languages do:

    Now, how to use it:

    Switching between cultures

    To test localization I added a ComboBox to my test form (in the release in your options dialog) and filled it with these Items (in the properties window):

    (machine default)
    English
    German
    French
    Gaelic

    Then the according event handler looks like:

    Imports System.Threading
    
    Private Sub CboLanguage_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CboLanguage.SelectedIndexChanged
        If CboLanguage.SelectedItem Is Nothing Then Return
    
        Dim Infos = {"", "en-US", "de-DE", "fr-FR", "gd-GB"}
    
        If CboLanguage.SelectedIndex = 0 Then
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentCulture
        Else
            Thread.CurrentThread.CurrentUICulture = New CultureInfo(Infos(CboLanguage.SelectedIndex))
        End If
    
    End Sub
    

    The list contains Gaelic only to test a language I really dont have in my resources, which means to test the switch to the default resource. In production you would delete the languages you don't have resources for from the list and of course the (machine default) entry.

    The two default cultures

    Note that there are two default cultures when you start an application. We have the default culture which is defined in your project properties about which I told before. But there is another one which is the computers language the app is running on. There are two scenarios how an app will find the culture it will use.

    1. You start the app, the app determines the computers culture and searches in your resources for the according ressources (this will happen every time a resource is needed). If the according resource is found, it will be used, otherwise the resource from the default resources will be used.

    2. The user sets another language in the options, this is saved in whatever settings strategy (My.Settings, app.config, INI-File, Registry, Isolated Storage, Database), you load it at Initialization/Start of your app and set the proper Threads UICulture, then this Culture will take place where the computers was. From now on resources will be searched in the according Resource file and analog, if not found, in the Default resources.

    Getting the texts

    Regarding the forms you have to do nothing. Depending on your Thread.CurrentThread.CurrentUICulture the according texts will be shown.

    As soon as you add a resource text to the default resources or one of your other language resource files VS will create a new property with the name of your resource text in then Resources.Designer.vb which is located in the "My Project" folder.

    This means, if you create a resource text named "New" and with value "&New" in the default resources or the Resources.en-US.resx file, the same with value "&Nouveau" in the Resources.fr-FR.resx file and "&Neu" in the Resources.de-DE.resx file, you can access the text with:

    My.Resources.New
    

    That's it. Depending on your Thread.CurrentThread.CurrentUICulture you will get the text in the according language.

    Transfer between Assemblies

    Note that the My.Resources namespace is declared with the Friend modifier which means it applies only to the local assembly. And every assembly has its own My.Resources namespace.

    If you do not Multithreading and every DLL has its own localized texts in the same cultures, you have no problem. But if you want to manage texts in a DLL from your main assembly then you need to have a reference of the main ResourceManager in your DLL. This looks like in your DLL class:

    Public Class MyPublicDLLClass
    
        ' Members
    
        Public Property ResourceManager As Resources.ResourceManager = Nothing
    
        ' Other members
    
    End Class
    

    And in the calling assembly:

    Dim DLLObject = New MyPublicDLLClass With {
        .ResourceManager = My.Resources.ResourceManager
    }
    

    Then you could do this to have access to the resources of the calling assembly:

    Function GetText(Item As String) As String
        Return GetText(Item, Nothing)
    End Function
    
    Function GetText(Item As String, [Default] As String) As String
        Dim Def As String = If([Default], Item)
    
        If ResourceManager IsNot Nothing Then
            Dim Text = ResourceManager.GetString(Item)
            Return If(String.IsNullOrEmpty(Text), Def, Text)
        Else
            Return Def
        End If
    End Function