I've written a WPF app in which user can change culture. The Method in app.xaml.cs looks like this :
public void SelectCulture(string culture)
{
// List all our resources
List<ResourceDictionary> dictionaryList = new List<ResourceDictionary>();
foreach (ResourceDictionary dictionary in Application.Current.Resources.MergedDictionaries)
{
dictionaryList.Add(dictionary);
}
// We want our specific culture
string requestedCulture = string.Format("Resources/StringResources.{0}.xaml", culture);
ResourceDictionary resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
if (resourceDictionary == null)
{
// If not found, we select our default language
//
requestedCulture = "Resources/StringResources.en-ES.xaml";
resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
}
// If we have the requested resource, remove it from the list and place at the end.\
// Then this language will be our string table to use.
if (resourceDictionary != null)
{
Application.Current.Resources.MergedDictionaries.Remove(resourceDictionary);
Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
}
// Inform the threads of the new culture
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
}
This allows to change on runtime every string resources from different resourcedictionnary.
But not every resources: those which are used in xaml via DynamicResource like this :
<GroupBox Header="{DynamicResource RootSettings}" >
are correctly updated.
But those which are used in C# code like in ViewModel like this :
stTestConnection = System.Windows.Application.Current.Resources["Connectionsucceded"].ToString();
do not update.
All string Resources are declared in different resources dictionaries like this :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib">
<!-- #region Global -->
<system:String x:Key="CriticalErrorTitle">Critical Error</system:String>
<system:String x:Key="ErrorTitle">Error</system:String>
<system:String x:Key="CloseApp">Application will close</system:String>
Any idea why the string resources used in C# are not updating but the ones used in XAML are ?
Ask me if more info needed.
Thanks
I had to do something similar recently with a runtime theme change performing a colour change using Xamarin in c#.
If I remember correctly then in Xaml with DynaminResource they updated automatically. But when in came to the c# code I had to do so manually. This was triggered by a xamarin OnThemeChanged.
If you have access to a similar method during culture change, you could put all your update logic there.
Related
I have a DropDown (using MahApps if that is important) that I'd like to use to switch the language "on the fly" in my program.
Language Class
namespace SAM
{
public class Language
{
public string Title { get; set; }
public string Culture { get; set; }
}
}
Change Language
private void DropLanguage_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
Language lang = DropLanguage.SelectedItem as Language;
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang.Culture);
}
I have all my strings as resources in Resources.resx (default) and Resources.en.resx (english)
3 issues I don't understand
When selecting "englisch" from the dropdown, the language does not change immediately but when I click sth. else, e.g. "close" (it asks "sure?"), the language has changed.
Strings that are directly in the .xaml file like <TextBlock Text="{x:Static p:Resources.Config_HeaderBar_Find_Speaker}" /> do not get updated at all.
Bonus: How would I switch back to the default language, as new CultureInfo(lang.Culture); expects one parameter and for the default I have Culture = null (as the Resources.resx has nothing in it's name). Changing the file to Resources.default.resx messes with my code a lot...
I tried to solve similar problem. The simplest solution for me was to move all Window content to UserControl and creating interface for window with method refreshLanguage(). Then I call from model:
private void SetLanguage(string cultureName)
{
var cul = new System.Globalization.CultureInfo(cultureName);
Properties.Resources.Culture = cul;
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = cul;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = cul;
System.Threading.Thread.CurrentThread.CurrentUICulture = cul;
System.Threading.Thread.CurrentThread.CurrentCulture = cul;
InitializeHamburgerMenu();
MainWindowView.RegreshLanguage();
RaisePropertyChanged("Title");
}
And RefreshLanguage in Window looks like:
public void RegreshLanguage()
{
GlobalUserControl content = new GlobalUserControl("Views/SettingsPage.xaml");
ContentPresenter_GlobalUserControl.Content = content;
}
In my case, UserControl provides navigation, so I passed last navigated URI as parameter. So, if you need to preserve state you can pass it as parameter to new UserControl. Recreating usercontrol cause all strings to reload without window recreation. Maybe good idea would be to call GC.Collect(); here, but depends on your scenario.
About default neutral culture. For me works to call SetLanguage("").
There are 2 things
Thread.CurrentThread.CurrentCulture
and
Thread.CurrentThread.CurrentUICulture
In order to set these valuse use the static method
CultureInfo.GetCultureInfo(String langCode)
where some examples of the parameter langCode are the following strings
"de-DE"
"en-US"
etc.
more info at
https://msdn.microsoft.com/en-us/library/yck8b540(v=vs.110).aspx
So, overall these lines of code sould work for change in German Language:
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("de-DE");
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
I have a WPF application in which I support multiple langauges. I can change the language runtime, as I load different sets of resource dictionaries containing the string resources upon change of language.
private void UpdateLoadedLanguages(object sender, SelectionChangedEventArgs e)
{
RemoveLoadedLanguages();
ResourceDictionary aResourceDictionary = new ResourceDictionary();
ResourceDictionary bResourceDictionary = new ResourceDictionary();
ResourceDictionary cLanguageResourceDictionary = new ResourceDictionary();
Language selectedLanguage = (Language) e.AddedItems[0];
switch (selectedLanguage)
{
case Language.enUS:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.en-US.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.en-US.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.en-US.xaml");
break;
case Language.nlNL:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.nl-NL.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.nl-NL.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.nl-NL.xaml");
break;
default:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.en-US.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.en-US.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.en-US.xaml");
break;
}
App.Current.Resources.MergedDictionaries.Add(aResourceDictionary );
App.Current.Resources.MergedDictionaries.Add(bResourceDictionary );
App.Current.Resources.MergedDictionaries.Add(cResourceDictionary );
}
A resource dictionary with the translations typically looks like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="DefaultApplicationTitleString">Title</system:String>
<system:String x:Key="DefaultDoneString">Done</system:String>
<system:String x:Key="DefaultCancelString">Cancel</system:String>
<system:String x:Key="DefaultDescriptionString">Description unknown</system:String>
<system:String x:Key="LanguageSelectionString">Language</system:String>
<system:String x:Key="AisleString">Aisle</system:String>
<system:String x:Key="LevelsString">levels</system:String>
</ResourceDictionary>
When I use one of the strings as defined above like directly below it works perfect.
Title="{DynamicResource DefaultApplicationTitleString}"
But with a converter like this ...
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string) Application.Current.FindResource("DefaultDescriptionString");
if (value != null)
{
string temp = (string) value;
char[] stringSeparators = { 'A', '.', '-' };
string[] stringElements= temp.Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);
string aisleString = (string) Application.Current.FindResource("AisleString");
string levelsString = (string) Application.Current.FindResource("LevelsString");
text = aisleString + " " + stringElements[1] + ", " + levelsString + " " + stringElements[2] + " - " +
stringElements[3];
}
return text;
}
... that I use like this ...
<TextBlock Text="{Binding Id, Converter={StaticResource ItemIdToTextConverter}}" />
... then my strings aren't updated when I change the language. If the property that I convert is updated on its viewModel then the language change does happen.
So my question: Does anyone have a solution for me where also my strings as a result of converters are updated on a language change?
My guesses for a solution are:
force an update of all frameworkelements that use a resource (How would I do this?)
or maybe do something with DynamicResourceExtenstion (I found this question: Return a dynamic resource from a converter, however I do not understand the explanation of the DynamicResourceExtension. So is this the way to go? If so, how would I implement this?)
This would be too much for a comment, but to little for an answer (or maybe not).
Converter is called only once when element is initializing. After that there is no link to what was the source if you are using StaticResource. If you use DynamicResource, then anytime when source get changed converter will be called again.
Do you change source in your localization approach? Nope.
Does converter gets invoked when language is changed? Yes, but only on notification from Id (PropertyChanged).
One way you can fix it is to subscribe your ViewModel to language switch event and call property changed event for each property. This will execute your converter despite source values still the same.
Other possibility would be to register somehow objects which required localized data. You will not use converter anymore, but an attached property (or behavior, depends on complexity). Enumerating (or keeping a list) over elements with such property and updating it shouldn't be hard.
Third way (which I am going to use in my very first localized wpf application) is reflection. Register all windows upong creation (un-register when unloading of course), define a schema of how you identify localizable properties of elements and go through it - win.
Regarding schema. My "localizable" controls with static content will be using already existing Uid to define a key, instead of defining things in separate resource dictionary it will be automatically generated). Dynamic ones I don't yet decide, perhaps there will be a behavior or attached property or even small peace of code behind (I like MVVM, but I am not making it an absolute) to reassign values from static class with predefined strings, which will be changed by reflection.
I am trying to load a custom control library via reflection in windows 8 Metro C# App, the library is loaded but the styles specified in generic.xaml are not loaded, eventually I tried to load the generic.xaml by making it as an Embedded resource and ,then extracted the Generic.xaml to a location and specified the location of it as uri of a ResourceDictionary object, but it throws an error
"Failed to create a 'System.Type' from the text local:CustomControl1"
I cannot create a nuget package or extension SDK as unfortunately that is not my requirement,
Below sample code I wrote to copy the generic.xaml and load it in a resource dictionary
public sealed class CustomControl1 : Control
{
public CustomControl1()
{
this.DefaultStyleKey = typeof(CustomControl1);
Assembly CurrentAssembly = typeof(CustomControl1).GetTypeInfo().Assembly;
var names = CurrentAssembly.GetManifestResourceNames();
var stream = CurrentAssembly.GetManifestResourceStream(names.First());
//generic.xaml is an embedded resource in the current assembly
if (stream != null)
{
//created new generic.xaml here
var file = ApplicationData.Current.LocalFolder.CreateFileAsync("Generic.xaml", CreationCollisionOption.ReplaceExisting).Completed = (o, a) =>
{
var storageFile = o.GetResults();
var s = storageFile.OpenStreamForWriteAsync().Result;
var length = (int)stream.Length;
byte[] bytes = new byte[length];
int output = stream.Read(bytes, 0, length);
s.Write(bytes, 0, length);
s.Flush();
s.Dispose();
var asyncResult = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var resourceDict = new ResourceDictionary();
var uri = new Uri("ms-appdata:///local/" + storageFile.Name);
resourceDict.Source = uri;
});
};
}
}
// OnApplyTemplate is not called without loading the style from generic.xaml
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
}
The below code I wrote in the custom control library's constructor, so that the control template can be set without generic.xaml
Here since the attribute TargeType="local:CustomControl1" is not present the control gets loaded properly, here since I loaded the style in the constructor, the OnApplyTemplate gets called
StringBuilder sb = new StringBuilder();
sb.Append(#"<ControlTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:d=""http://schemas.microsoft.com/expression/blend/2008""
xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006"">");
sb.Append(#"<Border Background=""{TemplateBinding
Background}""
BorderBrush=""{TemplateBinding BorderBrush}""
BorderThickness=""{TemplateBinding BorderThickness}"">
<Grid>
<Button x:Name=""Tbx1"" Content=""Hello World"" Foreground=""HotPink""
HorizontalAlignment=""Stretch"" VerticalAlignment=""Stretch""/>
</Grid>
</Border>");
sb.Append(#"</ControlTemplate>");
this.Template = (ControlTemplate)XamlReader.Load(sb.ToString());
but the problem is loading all the styles using XamlReader is not a good idea, unless we are out of Options. As there may be various dependent styles which too have to be loaded.
Check out how they do it in the WinRTXamlToolkit. They create the Generic.xaml for the project and include all control templates inside of separate styles in different ResourceDictionarys packaged next to the respective controls.
To put it more simply (for Templated controls, like you are using):
Make two files for each control, MyControl.cs and MyControl.xaml
In MyControl.cs in your MyControl constructor, set the StyleKey to be typeof(MyControl) (like you are doing currently).
Make sure there is a style for your control with TargetType set to the type of your control. Do the same thing for the ControlTemplate that you have as the Template property set in the Style.
In MyControl.xaml, make a ResourceDictionary that stores all of the necessary styles, templates, and resources.
In your Generic.xaml, create a MergedDictionaries tag under the root and create a ResourceDictionary for each control, setting the Source to the full path of MyControl.xaml
Set each of the .xaml files to be build type of Page with CustomTool set to MSBuild:Compile.
Hope this helps and Happy coding!
The problem I have is the following, I want to be able to have localization in my silverlight app without the usage of the usual resource file (since that approach did not work for me).
Any way I have several dictionaries for each language that I support, basically in the following:
Localization.xaml --> for the default language english
Localization.de.xaml --> for german
Localization.fr.xaml --> for french, I think you get the idea now
.
.
.
now when define the merged dictionaries in my app.xaml I would need to be able the dynamically define which Localization.xaml to use depending on the current culture.
I know I could do something like this in the code behind of the app.xaml:
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
try
{ ResourceDictionary dict = new ResourceDictionary()
{
Source = new Uri("/assembly;component/.../Localizations." + Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName + ".xaml", UriKind.Relative)
};
Resources.MergedDictionaries.RemoveAt(0);
Resources.MergedDictionaries.Add(dict);
}
catch(Exception){}
}
There are two concern that prevent me form using it however.
First is that it is in the code behind, since I am using MVVM I would like to avoid doing that.
Second is that I have to foumble with the MergedDictionaries, the 0-th dictionary is basically the default Localization.xaml. So if I were to change the position of that dictionary, I have to ensure that I also edit it in the code behind, which leaves room for errors.
So what I did was to write a SourceProvider, which give ne the Uri based on the current culture.
the code looks like this:
public class SourceProvider
{
public static Uri LocalizationSource
{
get
{
Uri source = new Uri("assembly;component/.../Localization." + Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName + ".xaml", UriKind.Relative);
try
{
ResourceDictionary dict = new ResourceDictionary(){Source = source};
}
catch(Exception)
{
source = new Uri("/assembly;component/.../Localization.xaml", UriKind.Relative);
}
return source;
}
}
}
Now in the App.xaml I can uses this as follows:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ns="clr-namespace:namespace of the sourceprovider"
x:Class="Peripherie.Configurator.App">
<Application.Resources>
<ResourceDictionary>
<ns:SourceProvider x:Key="Sourcer"/>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="{Binding LocalizationSource, Source={StaticResource Sourcer}}"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
This all works fine and I can bascially use the correct source, however rhis will give me the "Value does not fall within the expected range" error, which is kinda annoying.
Also all StaticResources used are not found because of the error as well, so it would show error which are not actually there, potentially flodding me with thousands of error for missing resources
Is there a way to prevent that ?
I'd like to add several resources to a ResourceDictionary from within one single line of XAML. I'll try to make an example out of it.
Imagine this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:util="clr-namespace:Util">
<util:MyResourceDictionaryA x:Key="MyResourceDictionaryA" />
<util:MyResourceDictionaryB x:Key="MyResourceDictionaryB" />
<util:MyResourceDictionaryC x:Key="MyResourceDictionaryC" />
<util:MyResourceDictionaryD x:Key="MyResourceDictionaryD" />
</ResourceDictionary>
I'd like to have something like:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:util="clr-namespace:Util">
<util:AllDictionaries x:Key="AllDictionaries" BaseName="MyResourceDictionary" />
</ResourceDictionary>
I've tried making a new ResourceDictionary like this:
public class AllDictionaries : ResourceDictionary
{
public string BaseName { get; set; }
public AllDictionaries ()
{
Clear();
var s = BaseName;
var DictionariesToLoad = new[] { "A", "B", "C", "D" };
foreach (var b in DictionariesToLoad )
{
var t = Type.GetType(b + s);
var resDir = Activator.CreateInstance(t) as ResourceDictionary;
Add(b + s, resDir);
}
}
}
However the constructor never gets called (or, not before something else in XAML references {StaticResource MyResourceDictionaryX} which hasn't been added to the application resources so it throws an exception). I also tried putting the code on the BaseName setter, to no avail.
This is an oversimplication of what I need, but I'm just trying to make the XAML more generic.
Any hints on how could one achieve this?
Note 1: the generated resource dictionaries need to be available (using their Key) resources for the rest of the XAML in the scope where I have created the AllDictionaries resource.
Note 2: for practical purposes, let's say I don't have access to MyResourceDictionaryX classes source code so I can't change anything on those
Merged Resource Dictionary already does what you want... it can be created through XAML or code.