View previous topic :: View next topic |
Author |
Message |
gunters63
Joined: 25 Jan 2011 Posts: 21
|
Posted: Thu Mar 17, 2011 1:51 pm Post subject: How to localize "formatted" strings? |
|
|
I want to localize a string like this in a WPF project:
"Click here to install (n) updates"
n should be bound to a property in the view model
The text itself should be localized using {ResX}.
Now there are several possible solutions:
1. Split text up with different TextBoxes.
TextBox 1: "Click here to install" (localized with {ResX})
TextBox 2: n bound to view model
TextBox 3: ") updates" (localized with {ResX})
Problem: Text is split up, ugly translation (loosing context). Ugly WPF
2. Using Binding.StringFormat
like this:
TextBox Text="{Binding Path="NumberOfUpdates" StringFormat="{Resx Key=xxx, ResxName=yyy}"
Resource xxx contains translated "Click here to install (n) updates" strings.
Unfortunately this only works if you don't switch languages on the fly. If you try you'll get a "Binding cannot be changed after it has been used" error in UpdateTarget in the MarkupExtension.
The reason: All properties of Binding are sealed after the binding got active the first time.
3. I tried a workaround using a MultiValueConverter using a ConverterParameter={ResX} and my own StringFormatter : IMultiValueConverter. This also does not work because ConverterParameter is sealed too.
4. Build the complete string in the ViewModel and bind to this. Example:
Code: |
public int NumberOfUpdates
{
get { return numberOfUpdates; }
set
{
numberOfUpdates = value;
RaisePropertyChanged(() => FormattedNumberOfUpdates);
}
}
public string FormattedNumberOfUpdates
{
get { return
string.Format(Strings.StartButtonUpdateCountFormatString, NumberOfUpdates); }
}
|
This works, but i find it ugly. Now i have some resources in resx of my views and this one ends up in a common resx of my view models.
Is there a better way to do this, in Xaml only and probably a converter?
A MultiBinding and a custom converter could work, but how do i bind to a ResX resource with {Binding} and not {ResX}?
My MultiValue converter would probably look like this:
Code: |
class StringFormatter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((values == null) || (values.Length == 0) || (values[0] == null) ||
(values[0] == DependencyProperty.UnsetValue))
{
return new[] {DependencyProperty.UnsetValue};
}
if (!(parameter is string))
{
return new[] {DependencyProperty.UnsetValue};
}
object[] parms = values.Skip(1).ToArray();
return culture == null
? string.Format((string) values[0], parms)
: string.Format(culture, (string) values[0], parms);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] {DependencyProperty.UnsetValue};
}
#endregion
}
|
And the first binding would be the format string, and the following the format parameters. |
|
Back to top |
|
|
Infralution
Joined: 28 Feb 2005 Posts: 5027
|
Posted: Thu Mar 17, 2011 9:57 pm Post subject: |
|
|
Solution 1 is, as you say, both ugly WPF and bad localization practice.
Solution 4 would be the normal solution to this problem. Is your main issue with this solution the fact that the resource ends up somewhere different?
To address this you can add your resource into the resx file that Globalizer generates for the XAML and the generate a code wrapper for the resx. To do this you select the resx file (in VB you will need to first select "Show All Files"). Then set the Custom Tool to "ResXFileCodeGenerator". You also need to set the Custom Tool Namespace (otherwise the generated wrapper class will have the same name as your XAML class. So if your base namespace is "WpfApp" you might set the namespace to "WpfApp.Resources".
Now you can add your string to the XAML resx file and access it from code like:
Code: | public string FormattedNumberOfUpdates
{
get { return string.Format(Resources.MyWindow.StartButtonUpdateCountFormatString, NumberOfUpdates); }
} |
The only issue with this is that the resource editor does not like the fact that the resource keys that Globalizer generates include "." and it outputs warnings. The custom tool will still work however and generate a code wrapper class where the "."s are replaced with "_". The warnings dissappear once you close the resource editor.
In the next major upgrade we are adding an option for XAML conversion which allows you to specify code compliant resource names should be used - which means you can eliminate these warnings. _________________ Infralution Support |
|
Back to top |
|
|
gunters63
Joined: 25 Jan 2011 Posts: 21
|
Posted: Thu Mar 17, 2011 10:18 pm Post subject: |
|
|
I am already using code generation for resx files if i need access to the resources via code.
Unfortunately my ViewModels and my Views live in different assemblies.
And, of course, the View assembly does not reference the ViewModel assembly .
That means that i can keep all WPF form string resources in the resx files of my views in the View assembly except the ones which require formatting. For those i have to supply properties in my ViewModels and i have to put the format strings in resx files inside the ViewModel assembly.
I was hoping that a pure XAML solution is possible (plus a custom converter). Like an alternate mean to access the resources not only view the ResX markup extension, but also via a Binding which would make the use of a MultiBinding possible.
Probably a Binding to a {Static} is possible, supplying the resource name as a Parameter. Or an ObjectProvider. It would be ok for me if i just extend the Infralution.Localization.Wpf project, for those few cases i don't really need support in the Globalizer.Net Tool. |
|
Back to top |
|
|
Infralution
Joined: 28 Feb 2005 Posts: 5027
|
Posted: Thu Mar 17, 2011 10:43 pm Post subject: |
|
|
Quote: | It would be ok for me if i just extend the Infralution.Localization.Wpf project, for those few cases i don't really need support in the Globalizer.Net Tool. |
You can modify the Infralution.Localization.Wpf project without necessarily affecting the support from Globalizer.NET. Globalizer is designed to dynamically attach to the assembly methods to allow for this. So provided you don't change the assembly or method names that Globalizer.NET attaches to you could modify the source code and use your own assembly.
Of course if you come up with a good solution we would be interested in incorporating into our baseline code. _________________ Infralution Support |
|
Back to top |
|
|
TravisWhidden
Joined: 26 Jan 2012 Posts: 8
|
Posted: Sun Jan 29, 2012 4:31 am Post subject: |
|
|
Just picked up this idea on StackOverflow.... ContentStringFormat is a dependency property. I just tested it out and it worked.
Code: | <ContentControl ContentStringFormat="{Resx ResxName=Blah.Blash.ViewName, Key=ResourceKey}"
Content="{Binding TotalIncidents}" /> |
http://stackoverflow.com/questions/685743/how-to-use-stringformat-in-xaml-elements _________________ Travis |
|
Back to top |
|
|
gunters63
Joined: 25 Jan 2011 Posts: 21
|
Posted: Mon Jan 30, 2012 6:54 am Post subject: |
|
|
I am pretty sure this only works as long as you don't switch languages on the fly. Have you tried this? |
|
Back to top |
|
|
TravisWhidden
Joined: 26 Jan 2012 Posts: 8
|
Posted: Mon Jan 30, 2012 7:45 pm Post subject: |
|
|
Yes, it worked when I changed cultures on the fly. I was pleased with that. Just didn't work for MultiBinding. I haven't had to do a MultiBinding yet, but I know it will come. This was just a nice Xaml workaround. _________________ Travis |
|
Back to top |
|
|
gunters63
Joined: 25 Jan 2011 Posts: 21
|
Posted: Mon Jan 30, 2012 7:52 pm Post subject: |
|
|
Probably this works as long as you have a separate string format dependency property, like ContentStringFormat.
Unfortunally only some WPF controls have such a property. TextBox for example doesn't have one, so you would have to use {Binding StringFormat}, but then language switching stops working.
Interesting find, though. Thanks for letting know. |
|
Back to top |
|
|
Infralution
Joined: 28 Feb 2005 Posts: 5027
|
Posted: Thu Feb 23, 2012 5:51 am Post subject: |
|
|
We have just released Version 3.2.0 of Globalizer which includes some new features in the Infralution.Localization.Wpf assembly to provide support for these scenarios.
To overcome the limitations of sealed Bindings the ResxExtension can now itself act like a binding. You simply set the binding properties (prefixed with "Binding") and the resource value is used as a format string. For instance the binding above would become:
Code: | <Resx Key="MyFormatString" BindingElementName="_fileListBox" BindingPath="SelectedItem"/> |
The ResxExtension also now supports formatting data from multiple data sources (similar to a MultiBinding) by nesting Resx elements. For instance:
Code: | <Resx Key="MyMultiFormatString">
<Resx BindingElementName="_fileListBox" BindingPath="Name"/>
<Resx BindingElementName="_fileListBox" BindingPath="SelectedItem"/>
</Resx> |
In this case you would define the MyMultiFormatString resource with placeholders for both data source arguments eg "Selected {0}: {1}". _________________ Infralution Support |
|
Back to top |
|
|
eduardo
Joined: 19 Jun 2012 Posts: 3
|
Posted: Tue Jun 19, 2012 7:53 pm Post subject: |
|
|
I have a resource string with a single placeholder with a value of: "{0} Name". And another resource string valued "First".
From the last posting I thought I might be able use the new extension release to get a result of "First Name" using XAML just by using the code below. It doesn't work though, apparently yielding an empty string.
Should I be able to do this in xaml this way? What am I missing if so?
Code: | <toolkit:WatermarkTextBox.Tag>
<Resx ResxName="Parties.Presentation.Resources.PersonDetailView" Key="PersonNameDescriptor">
<Resx ResxName="Parties.Presentation.Resources.PersonDetailView" Key="PreferredName_Label" />
</Resx>
</toolkit:WatermarkTextBox.Tag> |
|
|
Back to top |
|
|
Infralution
Joined: 28 Feb 2005 Posts: 5027
|
Posted: Wed Jun 20, 2012 12:39 am Post subject: |
|
|
The nesting of Resx elements only supports child resx elements which are bound to a data source ie you have set the BindingPath, BindingSource or BindingElementName for the child element. In this case the parent Resx entry should specify a place holder for each of the child bindings. The child resx key is not actually used (and doesn't need to be specified) since the child resx is only used to specify the data that the parent is bound to (just like a MultiBinding).
In your case if you could bind the child resx to a data element that returned "First" the you could use the MultiBinding syntax. For instance if I had a text block and set the hidden Tag property to use a resx string _textBlock_Tag = "First" then I could create a multi binding property like:
Code: | <TextBlock Name="_textBlock" Tag="{Resx _textBlock_Tag}">
<TextBlock.Text>
<Resx Key="_textBlock_Text">
<Resx BindingElementName="_textBlock" BindingPath="Tag" />
</Resx>
</TextBlock.Text>
</TextBlock> |
Where the resx string _textBlock_Text would be defined as "{0} Name". The resultant text in the text block would then be "First Name". _________________ Infralution Support |
|
Back to top |
|
|
|