How to use localization in C#
Asked Answered
A

9

302

I just can't seem to get localization to work.

I have a class library. Now I want to create resx files in there, and return some values based on the thread culture.

How can I do that?

Avatar answered 17/7, 2009 at 11:53 Comment(2)
P.S.: make sure you have installed the free Microsoft MAT (Multilingual App Toolkit) extension for visual studio ;-)Nerynesbit
https://mcmap.net/q/101707/-are-there-any-performance-issues-or-caveats-with-resource-resx-files-closedNerynesbit
K
635
  • Add a Resource file to your project (you can call it "strings.resx") by doing the following:
    Right-click Properties in the project, select Add -> New Item... in the context menu, then in the list of Visual C# Items pick "Resources file" and name it strings.resx.
  • Add a string resouce in the resx file and give it a good name (example: name it "Hello" with and give it the value "Hello")
  • Save the resource file (note: this will be the default resource file, since it does not have a two-letter language code)
  • Add references to your program: System.Threading and System.Globalization

Run this code:

Console.WriteLine(Properties.strings.Hello);

It should print "Hello".

Now, add a new resource file, named "strings.fr.resx" (note the "fr" part; this one will contain resources in French). Add a string resource with the same name as in strings.resx, but with the value in French (Name="Hello", Value="Salut"). Now, if you run the following code, it should print Salut:

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
Console.WriteLine(Properties.strings.Hello);

What happens is that the system will look for a resource for "fr-FR". It will not find one (since we specified "fr" in your file"). It will then fall back to checking for "fr", which it finds (and uses).

The following code, will print "Hello":

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
Console.WriteLine(Properties.strings.Hello);

That is because it does not find any "en-US" resource, and also no "en" resource, so it will fall back to the default, which is the one that we added from the start.

You can create files with more specific resources if needed (for instance strings.fr-FR.resx and strings.fr-CA.resx for French in France and Canada respectively). In each such file you will need to add the resources for those strings that differ from the resource that it would fall back to. So if a text is the same in France and Canada, you can put it in strings.fr.resx, while strings that are different in Canadian french could go into strings.fr-CA.resx.

Kelso answered 17/7, 2009 at 12:1 Comment(14)
The answer could make reference to the "behind-the-scenes" plumbing that is being done by Visual Studio here: resx.designer.cs file, making the intellisense work; satellite assemblies compiled with the class library, that need to be deployed with the compiled assembly and any later projects that use it, etc... The answer is nice and simple, but it doesn't help explain where things might go wrong eg if you don't use Visual Studio.Ious
+1 post! Rather than making files manually, try Zeta Resource Editor (zeta-resource-editor.com/index.html). It's free and helps you do these sorts of translations MUCH faster than just in VS.Newsman
Will .net pick up the OS language and then set the culture info automatically for you? ... or do we have to set the CurrentUICulture in the code for a different language?Enamour
@noelicus: as far as I know, it will default to the OS language. In my case I have an English Windows with Swedish locale set in the regional settings in the Control Panel, and CurrentUICulture and CurrentCulture defaults to en-US in my case.Algie
Great answer, nice and simple. just a point: I needed to add "using projectname.Resources;" to the class, or refer to the strings file by Resources.strings.stringName;Wadewadell
I had to use Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR"); Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("fr-FR");Lemmy
Access Modifier should be set Public for resource class to be generated. The class is not necesssarily in the Properties namespace, it's where you place the .resx file.Paradigm
how is this useful for form text and labels?Johnniejohnny
Is there a way to tell it which culture you want explicitly instead of relying on it checking the CurrentUICulture? For example, my culture could be "en-US" but I need to generate some text to send to someone else who uses "fr-CA". Would I have to temporarily change my CurrentUICulture to "fr-CA", get the string, then change the UI culture back, or is there some other way to pass in an arbitrary culture and get the correct string back?Markhor
Be aware that in VS 2017 resx with localization in winform is not working due to a bug (at least till version 15.4). A ticket is available: developercommunity.visualstudio.com/content/problem/63772/…Rotator
As of .NET 4.5 it is also possible to use System.Globalization.CultureInfo.DefaultThreadCurrentCulture instead of Thread.CurrentThread.CurrentUICulture so that you change the locale for the whole application instead of thread by threadSiloa
How can I set the textbox.text property using Properties.strings.MyTextBox on design UI ?Widera
This is weird because VS will create separate folders for each language and place a *.resources.dll inside each of them...Front
@NorbertBoros Thank you for this hint. I have to copy dlls regularly and my scripts for that missed those folders.Flossieflossy
M
50

It's quite simple, actually. Create a new resource file, for example Strings.resx. Set Access Modifier to Public. Use the apprioriate file template, so Visual Studio will automatically generate an accessor class (the name will be Strings, in this case). This is your default language.

Now, when you want to add, say, German localization, add a localized resx file. This will be typically Strings.de.resx in this case. If you want to add additional localization for, say, Austria, you'll additionally create a Strings.de-AT.resx.

Now go create a string - let's say a string with the name HelloWorld. In your Strings.resx, add this string with the value "Hello, world!". In Strings.de.resx, add "Hallo, Welt!". And in Strings.de-AT.resx, add "Servus, Welt!". That's it so far.

Now you have this generated Strings class, and it has a property with a getter HelloWorld. Getting this property will load "Servus, Welt!" when your locale is de-AT, "Hallo, Welt! when your locale is any other de locale (including de-DE and de-CH), and "Hello, World!" when your locale is anything else. If a string is missing in the localized version, the resource manager will automatically walk up the chain, from the most specialized to the invariant resource.

You can use the ResourceManager class for more control about how exactly you are loading things. The generated Strings class uses it as well.

Mcmullen answered 17/7, 2009 at 12:2 Comment(2)
how to set the locale?Clancy
@MatheusSimon: You don't need to. By default, the user's current locale is used. If you want to force a certain locale (e.g. for allowing users to change the language manually), you have to set System.Threading.Thread.CurrentCulture and CurrentUICulture in each thread, likely before any resources are loaded for the first time. It's easier to restart an application for this rather than update at runtime.Mcmullen
E
19

In addition @Fredrik Mörk's great answer on strings, to add localization to a form do the following:

  • Set the form's property "Localizable" to true
  • Change the form's Language property to the language you want (from a nice drop-down with them all in)
  • Translate the controls in that form and move them about if need be (squash those really long full French sentences in!)

Edit: This MSDN article on Localizing Windows Forms is not the original one I linked ... but might shed more light if needed. (the old one has been taken away)

Enamour answered 20/9, 2012 at 10:51 Comment(2)
The msdn article is not available anymore, any replacement?Nineveh
Not sure - I've linked the best one I could see, but I can't remember what the article was like 7 years ago ;)Enamour
T
14

Great answer by F.Mörk. But if you want to update translation, or add new languages once the application is released, you're stuck, because you always have to recompile it to generate the resources.dll.

Here is a solution to manually compile a resource dll. It uses the resgen.exe and al.exe tools (installed with the sdk).

Say you have a Strings.fr.resx resource file, you can compile a resources dll with the following batch:

resgen.exe /compile Strings.fr.resx,WpfRibbonApplication1.Strings.fr.resources 
Al.exe /t:lib /embed:WpfRibbonApplication1.Strings.fr.resources /culture:"fr" /out:"WpfRibbonApplication1.resources.dll"
del WpfRibbonApplication1.Strings.fr.resources
pause

Be sure to keep the original namespace in the file names (here "WpfRibbonApplication1")

Typewriting answered 18/7, 2012 at 9:24 Comment(1)
Thanks for the comment on preserving the namespace (y), which - if missed - won't generate any errors, but simply revert to the fallback resource.Candancecandela
P
13

A fix and elaboration of @Fredrik Mörk answer.

  • Add a strings.resx Resource file to your project (or a different filename)
  • Set Access Modifier to Public (in the opened strings.resx file tab)
  • Add a string resouce in the resx file: (example: name Hello, value Hello)
  • Save the resource file

Visual Studio auto-generates a respective strings class, which is actually placed in strings.Designer.cs. The class is in the same namespace that you would expect a newly created .cs file to be placed in.

This code always prints Hello, because this is the default resource and no language-specific resources are available:

Console.WriteLine(strings.Hello);

Now add a new language-specific resource:

  • Add strings.fr.resx (for French)
  • Add a string with the same name as previously, but different value: (name Hello, value Salut)

The following code prints Salut:

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
Console.WriteLine(strings.Hello);

What resource is used depends on Thread.CurrentThread.CurrentUICulture. It is set depending on Windows UI language setting, or can be set manually like in this example. Learn more about this here.

You can add country-specific resources like strings.fr-FR.resx or strings.fr-CA.resx.

The string to be used is determined in this priority order:

  • From country-specific resource like strings.fr-CA.resx
  • From language-specific resource like strings.fr.resx
  • From default strings.resx

Note that language-specific resources generate satellite assemblies.

Also learn how CurrentCulture differs from CurrentUICulture here.

Paradigm answered 28/11, 2016 at 19:24 Comment(0)
N
2

In general you put your translations in resource files, e.g. resources.resx.

Each specific culture has a different name, e.g. resources.nl.resx, resources.fr.resx, resources.de.resx, …

Now the most important part of a solution is to maintain your translations. In Visual Studio install the Microsoft MAT tool: Multilingual App Toolkit (MAT). Works with winforms, wpf, asp.net (core), uwp, …

In general, e.g. for a WPF solution, in the WPF project

  • Install the Microsoft MAT extension for Visual Studio.
  • In the Solution Explorer, navigate to your Project > Properties > AssemblyInfo.cs
  • Add in AssemblyInfo.cs your default, neutral language (in my case English): [assembly: System.Resources.NeutralResourcesLanguage("en")]
  • Select your project in Solution Explorer and in Visual Studio, from the top menu, click "Tools" > "Multilingual App Toolkit" > "Enable Selection", to enable MAT for the project.
    • Now Right mouse click on the project in Solution Explorer, select "Multilingual App Toolkit" > "Add translation languages…" and select the language that you want to add translations for. e.g. Dutch.

What you will see is that a new folder will be created, called "MultilingualResources" containing a ....nl.xlf file.

The only thing you now have to do is:

  1. add your translation to your default resources.resx file (in my case English)
  2. Translate by clicking the .xlf file (NOT the .resx file) as the .xlf files will generate/update the .resx files.

(the .xlf files should open with the "Multilingual Editor", if this is not the case, right mouse click on the .xlf file, select "Open With…" and select "Multilingual Editor".

Have fun! now you can also see what has not been translated, export translations in xlf to external translation companies, import them again, recycle translations from other projects etc...

More info:

Nerynesbit answered 31/1, 2020 at 8:9 Comment(0)
C
1

In my case

[assembly: System.Resources.NeutralResourcesLanguage("ru-RU")]

in the AssemblyInfo.cs prevented things to work as usual.

Crossstitch answered 13/5, 2015 at 7:16 Comment(0)
L
0

In addition to @Eric Bole-Feysot answer:

Thanks to satellite assemblies, localization can be created based on .dll/.exe files. This way:

  • source code (VS project) could be separated from language project,
  • adding a new language does not require recompiling the project,
  • translation could be made even by the end-user.

There is a little known tool called LSACreator (free for non-commercial use or buy option) which allows you to create localization based on .dll/.exe files. In fact, internally (in language project's directory) it creates/manages localized versions of resx files and compiles an assembly in similar way as @Eric Bole-Feysot described.

Larose answered 11/2, 2015 at 0:17 Comment(0)
M
0

You could use Lexical.Localization¹ which allows embedding default value and culture specific values into the code, and be expanded in external localization files for futher cultures (like .json or .resx).

public class MyClass
{
    /// <summary>
    /// Localization root for this class.
    /// </summary>
    static ILine localization = LineRoot.Global.Type<MyClass>();

    /// <summary>
    /// Localization key "Ok" with a default string, and couple of inlined strings for two cultures.
    /// </summary>
    static ILine ok = localization.Key("Success")
            .Text("Success")
            .fi("Onnistui")
            .sv("Det funkar");

    /// <summary>
    /// Localization key "Error" with a default string, and couple of inlined ones for two cultures.
    /// </summary>
    static ILine error = localization.Key("Error")
            .Format("Error (Code=0x{0:X8})")
            .fi("Virhe (Koodi=0x{0:X8})")
            .sv("Sönder (Kod=0x{0:X8})");

    public void DoOk()
    {
        Console.WriteLine( ok );
    }

    public void DoError()
    {
        Console.WriteLine( error.Value(0x100) );
    }
}
Michi answered 8/6, 2019 at 12:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.