Infralution Support Forum Index Infralution Support
Support groups for Infralution products
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

How to localize "formatted" strings?

 
Post new topic   Reply to topic    Infralution Support Forum Index -> Globalizer Support
View previous topic :: View next topic  
Author Message
gunters63



Joined: 25 Jan 2011
Posts: 21

PostPosted: Thu Mar 17, 2011 1:51 pm    Post subject: How to localize "formatted" strings? Reply with quote

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
View user's profile Send private message
Infralution



Joined: 28 Feb 2005
Posts: 5027

PostPosted: Thu Mar 17, 2011 9:57 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
gunters63



Joined: 25 Jan 2011
Posts: 21

PostPosted: Thu Mar 17, 2011 10:18 pm    Post subject: Reply with quote

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 Smile.

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
View user's profile Send private message
Infralution



Joined: 28 Feb 2005
Posts: 5027

PostPosted: Thu Mar 17, 2011 10:43 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
TravisWhidden



Joined: 26 Jan 2012
Posts: 8

PostPosted: Sun Jan 29, 2012 4:31 am    Post subject: Reply with quote

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
View user's profile Send private message
gunters63



Joined: 25 Jan 2011
Posts: 21

PostPosted: Mon Jan 30, 2012 6:54 am    Post subject: Reply with quote

TravisWhidden wrote:
Just picked up this idea on StackOverflow.... ContentStringFormat is a dependency property. I just tested it out and it worked.

Code:
 <ContentControl>


http://stackoverflow.com/questions/685743/how-to-use-stringformat-in-xaml-elements


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
View user's profile Send private message
TravisWhidden



Joined: 26 Jan 2012
Posts: 8

PostPosted: Mon Jan 30, 2012 7:45 pm    Post subject: Reply with quote

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
View user's profile Send private message
gunters63



Joined: 25 Jan 2011
Posts: 21

PostPosted: Mon Jan 30, 2012 7:52 pm    Post subject: Reply with quote

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
View user's profile Send private message
Infralution



Joined: 28 Feb 2005
Posts: 5027

PostPosted: Thu Feb 23, 2012 5:51 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
eduardo



Joined: 19 Jun 2012
Posts: 3

PostPosted: Tue Jun 19, 2012 7:53 pm    Post subject: Reply with quote

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
View user's profile Send private message
Infralution



Joined: 28 Feb 2005
Posts: 5027

PostPosted: Wed Jun 20, 2012 12:39 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    Infralution Support Forum Index -> Globalizer Support All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group