Problem
I am trying to implement a system in my program to switch language. I found out that i can use CultureInfo and ResourceManager to achieve that. I built up this code after a couple of hours having problem with the resource not found, finally i found and answer here on stackoverflow and i arranged the following code:
CultureInfo culture;
culture = CultureInfo.CreateSpecificCulture("it-IT");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
Assembly resourceAssembly = Assembly.Load("MY ASSEMBLY NAME");
ResourceManager manager = Properties.Resources.ResourceManager;
string[] manifests = resourceAssembly.GetManifestResourceNames();
string manifest = manifests[0].Replace(".resources", string.Empty);
manager = new ResourceManager(manifest, resourceAssembly);
string greeting = String.Format("The current culture is {0}.\n{1}",
Thread.CurrentThread.CurrentUICulture.Name,
manager.GetString("HelloString"));
MessageBox.Show(greeting);
Since this is a really big program with a lot of pages, windows and usercontrols, i need to access the language from a lot of different files.
The code i posted above, should look into the root of my Solution and look for a file named it-IT.resx. it says that the current culture is it-IT but it doesn't write the value of HelloString, but it doesn't give any error so it's definitely a problem with Resource Manager but i don't know why it doesn't crash saying it doesn't find the resource. I am sure that inside the resx file there is a value called HelloString.
Is there a reason to load the types dynamically with Assembly.Load()? It is asking for trouble. Another way would be adding a static assembly reference.
This approach has many advantages:
the resource names can be accesses as property names, it is comfortable
the calling code uses always existing resources
in the calling code there are no references to non-existing resources
To be able to use resources from another assembly the resource access modifier needs to be set to public:
If the resources file is named RText, as in the example above, the value of a resource can retrieved from another project with:
var val = ProjectWithResourcesNamespace.RText.HelloString;
Related
I try to make my application multilingual. For this I have created two resource files:
Resource.de-DE.resx
Resource.en-US.resx
Both are set to public and contain the same keyword and translation. I can also access the keywords in the backend -> see image.
Unfortunately my program does not load the files when I change the language.
My steps:
set the default Resources.resx to public.
copied this file twice
renamed file to (see above)
keywords+translation entered and checked
project created again
included on the Button-Content by:
<Button x:Name="Home_Button" Style="{DynamicResource Menuebutton}" Content="{x:Static p:Resources.Menue_home}" Click="Home_Click"/>
access of the object set by:
xmlns:p="clr-namespace:VS_Launcher.Properties"
include in the MainWindow.xaml.cs by:
CultureInfo myCultureInfo = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentCulture = myCultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = myCultureInfo;
Thread.CurrentThread.CurrentCulture = myCultureInfo;
Thread.CurrentThread.CurrentUICulture = myCultureInfo;
In another project I did the same thing. But there the solution worked. What is my error?
Resource Files
Directory structure
Are you sure the file names of alternative language resources (excluding culture-locale) are identical with default one? If the default one is Resources.resx, alternative ones must be like:
Resources.de-DE.resx
Resources.en-US.resx
I am having issues with the already mentioned NuGet package. I have created a folder named Resources, where I have inserted the following files:
Strings.en.resx
Strings.sk.resx (my default language)
I am using Prism, where my MainWindow (plain container) is located under root window and all the other pages are located under Views folder.
I have added the following to my MainPage view:
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:LocalizeDictionary.OutputMissingKeys="True"
lex:ResxLocalizationProvider.DefaultAssembly="eManagement"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
and here is an example of a TextBlock:
<TextBlock Text="{lex:Loc Key=Greetings}"/>
The problem is, that if I place the Strings.resx file in the root folder, the control is translated without a problem. But when I want to keep my translations under a Resources folder (meaning that english translation will be under Resources/Strings.en.resx), it suddenly does not work.
What do I need to change in order to make it work?
I was missing the following line:
lex:InheritingResxLocalizationProvider.DefaultDictionary="Strings"
Plus in the code-behind I had to manually set the culture as otherwise my culture was always invariant
#region Language Selection
var culture = new CultureInfo(Properties.Settings.Default.LanguageSettings);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
WPFLocalizeExtension.Engine.LocalizeDictionary.Instance.Culture = Thread.CurrentThread.CurrentCulture;
#endregion
This has solved the problem.
In my WPF appliation, I have property defined as following
[Required(AllowEmptyStrings =false, ErrorMessageResourceName ="Msg1", ErrorMessageResourceType =typeof(*<AssemblyName>*.Resources.*<ResourceFileName>*))]
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
_name = value;
}
}
I have my error messages defined in separate assembly which has resource file for different cultures e.g. Resources.resx, Resources.en-GB.Resx, Resources.fr-FR.Resx, Resources.en-US.Resx, etc.
With above code in place, I'm able to retrieve the error message from default resource file in my satellite assembly but I don't see any provision to find the string resource from culture specific resource file. What I mean is if my CurrentUICluture is set as english(United Kingdom) then I want to retrieve the resource value from the file "Resources.en-GB.Resx" instead of the default file (i.e. Resources.Resx).
I don't see any way to pass the culture info in the Required attribute definition. Also, I have tested that it is not inherently look into the culture specific resource file based on the current culture set.
What I want is some way to make the resource retrieval mechanism culture aware.
Thanks,
I came up with an easy idea that saves me from decorating all properties individually.
Indeed a nasty solution because it involves reflection but works.
Caution: this can break if the .NET Core team decide to rename fields or classes or if internal stuff change, so use at your own discretion.
First create a resx file in your app be either creating one from scratch or by copying this or this one, the replace the strings with the desired translations, in the same format as they appear (pay attention to {0} placeholders and to their order).
Obviously, you can then add DataAnnotations.en-UK.resx-like files and translate them all, as long as your app is properly set to operate with the desired culture, it'll work.
In your project, call the following at early startup:
void InitializeDataAnnotationsCulture()
{
var sr =
typeof(ValidationAttribute)
.Assembly
.DefinedTypes
.Single(t => t.FullName == "System.SR");
var resourceManager =
sr
.DeclaredFields
.Single(f => f.IsStatic && f.Name == "s_resourceManager");
resourceManager
.SetValue(null,
DataAnnotationsResources.ResourceManager, /* The generated RESX class in my proj */
BindingFlags.NonPublic | BindingFlags.Static, null, null);
var injected = resourceManager.GetValue(null) == DataAnnotationsResources.ResourceManager;
Debug.Assert(injected);
}
What it does is it takes advantage of this field to replace the default DataAnnotations resource manager with your own.
It's been tested and works in .NET Core 3.1 WPF, but you can browse the .NET Source of any .NET to find the resource location and inject your own resources.
If you'd like to make your website culture aware, you'll need to modify the globalization attribute in the web.config under the system.web element:
<globalization fileEncoding="utf-8" requestEncoding="utf-8" culture="auto" uiCulture="auto" enableClientBasedCulture="true" />
Then it will look for resources based on the browser's preferred language settings.
Alternatively, you can specify it explicitly:
<globalization fileEncoding="utf-8" requestEncoding="utf-8" responseEncoding="" culture="en-US" uiCulture="en-US" />
Finally I got the fix for my problem from this link
In my app, I was setting the culture at the time of starting the application by putting following code in app.xaml.cs file.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
InitializeCultures();
}
private static void InitializeCultures()
{
var culture = ConfigurationManager.AppSettings.Get("Culture");
if (!String.IsNullOrEmpty(culture))
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
}
var UICulture = ConfigurationManager.AppSettings.Get("UICulture");
if (!String.IsNullOrEmpty(UICulture))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(UICulture);
}
}
However problem is even if I set the Culture initially, not all threads will use the same culture set by me initially. So the thread which was reading the resource value for me was still using the default culture and this resulted in reading of resource string always from default culture resource file rather then the culture specific resource file.
So the trick was to set all the threads in my app to use the same culture which I set initially. There are two properties for this
CultureInfo.DefaultThreadCurrentCulture
CultureInfo.DefaultThreadCurrentUICulture
Setting required culture value to these two properties initially will ensure that all the subsequent threads used in the application will have the same culture as set by user. These two properties sets the culture on all the threads in current app domain.
Once culture is set properly, reading of resource values inherently becomes culture aware and reads resource values from culture specific resource files.
So following is the updated version of InitializeCulture method :
private static void InitializeCultures()
{
var culture = ConfigurationManager.AppSettings.Get("Culture");
if (!String.IsNullOrEmpty(culture))
{
Thread.CurrentThread.CurrentCulture = CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(culture);
}
var UICulture = ConfigurationManager.AppSettings.Get("UICulture");
if (!String.IsNullOrEmpty(UICulture))
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(UICulture);
}
}
P.S. There is a word of caution of using this fix in web application. Pls read about it on original link.
Hope this helps...
My C#/WF application uses some files, which are different for different languages. These file can not be placed as resources in satellite assemblies. I wish however to put them in the same directories as the satellite assemblies reside, but I need to know actually where the assembly resides (including the situation, when default language embedded in the binary file is used).
For example, when application switches automatically to polish language, I wish to retreive location:
D:\<app folder>\pl-PL\
Is there a way to do so? Please note, that I wish to retreive this information from the assembly and not by guessing the folder location.
With help of Steve B, here's a solution:
string FindSatelliteAssemblyLocation(CultureInfo culture)
{
if (culture == CultureInfo.InvariantCulture)
return Path.GetDirectoryName(Application.ExecutablePath);
try
{
Uri location = new Uri(Assembly.GetExecutingAssembly().GetSatelliteAssembly(culture).CodeBase);
return Path.GetDirectoryName(location.LocalPath);
}
catch
{
return FindSatelliteAssemblyLocation(culture.Parent);
}
}
You may use the current thread UI culture to get the language :
var subfolder = System.Threading.Thread.CurrentUICulture.Name;
I feel like you could use a combination of System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) and CultureInfo.CurrentUICulture to figure this out.
For example, if your assembly is located in C:\Program Files\MyCompany\App\ and the Current UI Culture is en_US, you could combine the two:
string exeDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
string culture = CultureInfo.CurrentUICulture.Name;
string langDir = System.IO.Path.Combine(exeDir, culture);
To produce "C:\Program Files\MyCompany\App\en_US"
I have created an assembly and later renamed it.
Then I started getting runtime errors when calling:
toolsMenuName = resourceManager.GetString(resourceName);
The resourceName variable is "enTools" at runtime.
Could not find any resources
appropriate for the specified culture
or the neutral culture. Make sure
"Jfc.TFSAddIn.CommandBar.resources"
was correctly embedded or linked into
assembly "Jfc.TFSAddIn" at compile
time, or that all the satellite
assemblies required are loadable and
fully signed.
The code:
string resourceName;
ResourceManager resourceManager = new ResourceManager("Jfc.TFSAddIn.CommandBar", Assembly.GetExecutingAssembly());
CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);
if(cultureInfo.TwoLetterISOLanguageName == "zh")
{
CultureInfo parentCultureInfo = cultureInfo.Parent;
resourceName = String.Concat(parentCultureInfo.Name, "Tools");
}
else
{
resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
}
toolsMenuName = resourceManager.GetString(resourceName); // EXCEPTION IS HERE
I can see the file CommandBar.resx included in the project, I can open it and can see the "enTools" string there. It seems that either resources are not included into assembly or resource are included but .NET cannot resolve the name.
I think simpler solution would be to create separate resources file for each language.
As far as this case is concerned check if the assembly containing resources has the default namespace set to the same text (Project->Properties->Default namespace; in VS)
Check as well if the resx file has a property BuildAction set to "Embedded resource"
Sounds similar to an issue we had. The namespace was incorrect in the resource file's designer. I fixed it by manually re-running the custom-tool on the resx file.
Right click your.resx, and click Run Custom Tool.
I'm sure you've already got the answer, but just in case:
You can view your ManifestResourceName by calling
System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames()
Check that Manifest name and your name in GetString() calling are identical.
Also, be sure you have correct namespace in designer.resx file:
namespace Jfc.TFSAddIn {
...
global::System.Resources.ResourceManager temp =
new global::System.Resources.ResourceManager(
"Jfc.TFSAddIn.CommandBar", typeof(CommandBar).Assembly);
...
}
Open resx file properties: "Build Action" should be "Embedded Resource"
For me, the source of the problem was naming the rex files starting with a number:
20160216_tranlation.resx
I had to add an underscore _ before the resx file name when calling GetGlobalResourceObject:
public static string getResource(string key)
{
return HttpContext.GetGlobalResourceObject("_20160216_tranlation", key).ToString();
}
I corrected the namespace in designer file (Resources.Designer.cs) in ResourceManager static property & it worked for me.
See the code below:
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XYZAssembly.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
I added a temporary class within my Form.cs while (testing || debugging) that caused this exception to be thrown. The Form.resx file (Name || Resource ID) was modified to the temporary class name instead of the Form class name. This caused the issue for me. I (corrected || alleviated) this by creating a separate file for my temporary class in the project.
One Solution is to change the property of resx file from content to Embedded Resource and Build it.Sure this time u vil get
I have encountered this issue in Xamarin.Forms, when I tried to the rename the project, the resources could not be loaded anymore with the same stated error text.
To fix the problem I had to modify the .csproj by a text editor, and change the logical name of the embedded resource.
<EmbeddedResource Include="Localization\TextResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TextResources.Designer.cs</LastGenOutput>
<LogicalName>YourNewNamespaceName.TextResources.resources</LogicalName>
<SubType>Designer</SubType>
</EmbeddedResource>
Also watch out for the autogenerated class when you rebuild it, the namespace stated in there might change.
Hope it helps someone that went into the same situation.
Got this error when I added a class ABOVE the partial form class in my Windows forms app.
It went away when I moved the class BELOW the partial form class.
This answer solved the problem for me! GetGlobalResourceObject