WPF Localization – On-the-fly Language Selection

October 30, 2007

73 comments

Download code ver 1.3 from here.

 

Many of my customers asked me:

How WPF supports localization and globalization?

Is there any built-in mechanism for choosing a language on-the-fly, at runtime and without closing any window?

How do I translate formatted text with parameters?

Well…, you know the story,… aren’t you!? No? So start by reading this, and this. Still confused? continue with this and also this.

 

As you can see, there is more than one solution. Each has its pros and cons. The WPF official language support provides a complex localization set of API’s, and a bizarre SDK tool for translation and creation of localized assemblies. Others provide unofficial great ideas for localizing WPF based on .NET 2.0 resource files, WPF resource dictionary and XML.

 

I decided to share another mechanism for localizing WPF applications.

Download the code from here.

 

Why did I bother to invent another solution, and why would you want to use my solution

  1. It provides an option for replacing languages at runtime, on-the-fly
  2. It performs better than a lame XML, XPath based binding solution
  3. It can be used via Styles, Control Templates and Data Templates
  4. It translates a formatted text with parameters, using default and custom formatters
  5. It provides a Translate custom markup-extension to write an elegant XAML

 

I will start by discussing the markup snippet bellow:

<Window x:Class="Tomers.WPF.Localization.MainWindow" x:Name="Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:loc="http://schemas.tomer.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Tomers.WPF.Localization"
xmlns:d="http://schemas.microsoft.com/expression/blend/2006"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"

Background="{StaticResource WindowBrush}"

loc:Translate.Uid="0"

Title="{loc:Translate}"
Width="{loc:Translate}"
Height="{loc:Translate}"
FlowDirection="{loc:Translate}">

<Window.Resources>
<DataTemplate DataType="{x:Type local:Data}">
<TextBlock Margin="8" HorizontalAlignment="Left">
<TextBlock.Text>
<loc:Translate>
<Binding Path
="Uid" />
<Binding Path
="ID" />
</loc:Translate
>
</TextBlock.Text>
<TextBlock.Foreground>
<loc:Translate>
<Binding Path
="Uid" />
</loc:Translate
>
</TextBlock.Foreground>
</TextBlock>
</DataTemplate>
</Window.Resources>

<StackPanel x:Name="_panel">

<Label loc:Translate.Uid="1"
Content="{loc:Translate}"
FontSize="14" />

<TextBlock loc:Translate.Uid="2" FontSize="16">
<TextBlock.Text>
<loc:Translate>
<Binding ElementName="Root" Path
="Width" />
<Binding ElementName="Root" Path
="Height" />
</loc:Translate
>
</TextBlock.Text>
</TextBlock>

<TextBlock loc:Translate.Uid="3"
Text="{loc:Translate}"
Background="{loc:Translate}"
Width="{loc:Translate}"
Height="{loc:Translate}" FontSize="18" />

<TextBlock FontSize="18">
<TextBlock.Text>
<loc:Translate>
<Binding ElementName="Root" Path
="Uid" />
</loc:Translate
>
</TextBlock.Text>
</TextBlock>

<ContentControl x:Name="_elementI" FontSize="20" />
<ContentControl x:Name="_elementII" FontSize="20" />

<Button loc:Translate.Uid="7"
Margin="8"
HorizontalAlignment="Right"
Content="{loc:Translate}"
Width="200"
Click="Button_Click" FontSize="16" />

</StackPanel>
</
Window>

image image   imageimage

As you can see, the markup snippet produces the window above. When clicking the “Click to change language” button, all the elements attached with the “loc:Translate” custom markup-extension, are automatically have new values: In our case the language, alignment, size, position, color and other.

 

So why did I choose to use a custom markup extension, what’s wrong with a simple Data Binding markup

Well, this is the magic of my solution. It is possible to use a complex binding expression indeed, for example:

<Label Content="{Binding Source={x:Static loc:LanguageContext.Instance}, Path=Dictionary, Mode=OneWay
Converter={StaticResource languageConverter}, ConverterParameter=2}
" />

But as you can see, it’s very long expression. Now try to imagine how long it will be if you pass parameters…

 

What’s in the package

There are few types you should know before you start localizing:

  1. LanguageDictionary – This is an abstract class. It provides an abstraction for translating values by exposing two important methods: Load and Translate. The Load method loads the repository with data for each locale, and the Translate method translates a key-value pair into locale value from the repository. For example, I wrote a XmlLanguageDictionary class that loads and stores an XML language file inside a dictionary (you can find it in the LocalizationDemo project).
  2. LanguageContext – This singleton type holds the culture and the dictionary of the active language. It also plays an important role by notifying on culture change.
  3. LanguageConverter – This important type calls the active dictionary to translate values each time a language is replaced. The language converter is instantiated by the TranslateExtesnion during the internal-binding operation (see TranslateExtesnion type bellow). The LanguageConverter implements both IValueConverter and IMultiValueConverter interfaces.
  4. TranslateExtension – This is the magic of this solution. It provides the user an option to bind a localized-property to the language dictionary using a simple and short markup within XAML, as demonstrated in the markup snippet above. By extracting the target element and the dependency property, it binds the LanguageContext.Dictionary to the targets property.

 

How to use it

First, you have to create your own language-dictionary by deriving LanguageDictionary. You can also start by using my XmlLanguageDictionary, provided as a lame example inside the LocalizationDemo project.

Second, you should create and register a dictionary instance for each language and select the default one, before you create any localized UI element. See the markup bellow:

    /// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
LanguageDictionary.RegisterDictionary(
CultureInfo.GetCultureInfo("en-US"),
new XmlLanguageDictionary("Languages/en-US.xml"));

LanguageDictionary.RegisterDictionary(
CultureInfo.GetCultureInfo("he-IL"),
new XmlLanguageDictionary("Languages/he-IL.xml"));

LanguageContext.Instance.Culture = CultureInfo.GetCultureInfo("en-US");

base.OnStartup(e);
}
}

Now, use the Translate markup extension to localize elements as demonstrated above.

 

To translate a simple element with no parameters use the following syntax (The uid value, Content and FontSize should be keys in your dictionary back-storage – see my xml files for an example):

<Label loc:Translate.Uid="1" Content="{loc:Translate}" FontSize="{loc:Translate}" />

 

To translate an element with parameters use the following syntax

<TextBlock loc:Translate.Uid="2" FontSize="16">
<TextBlock.Text>
<loc:Translate>
<Binding ElementName="Root" Path
="Width" />
<Binding ElementName="Root" Path
="Height" />
</loc:Translate
>
</TextBlock.Text>
</TextBlock>

 

To translate an element with a dynamic Uid, use the previous syntax, where the Uid must be the first parameter (this is a great solution for styles and templates):

<TextBlock FontSize="18">
<TextBlock.Text>
<loc:Translate>
<Binding ElementName="Root" Path="Uid" />
</loc:Translate>
</TextBlock.Text>
</TextBlock>

 

There are several more issues that should be considered, also the code should be refactored for production. Use it on your own risk.

Download code ver 1.0 from here.

Download code ver 1.1 from here.

 

Update: On 13-Aug-2008

- Markup extension bug fixed

- Fully supported on both Blend and Cider designers

- Default fallback value was added

- Demo visuals improvements

Download code ver 1.2 from here.

 

Update: On 06-Feb-2009

- Uid’s can be provided directly via Translate markup (now works with Setters – see demo)

- Demo upgrade to support new feature

Download code ver 1.3 from here.

 

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

73 comments

  1. C. MariusNovember 9, 2007 ב 17:44

    Hi,

    How about support for Blend 2(September CTP). As I lost design support in Blend, it gets pretty impossible to use. I rather use the simple solution of Content={x:Static p:Resources.MyResourceName} which offers design-time support for VS2008 and Blend. Isn’t something that could be done for this?

    Thank you (c_marius@msn.com)

    Reply
  2. Tomer ShamamNovember 11, 2007 ב 14:15

    Hi Marius,

    Indead, there is a problem with Blend and Custom Markup Extensions. Blend 2 can deal with it, but not 100% (you will not get the translated values at design time, but at least it will show you the UI).

    You can always use a Binding expression with a custom Value Converter instead of my Translate extension, but you should write alot of markup for each element (Path, Source, Converter, Parameter, etc).

    Currently, I’m working on another solution for design time, using Data Provider.

    Reply
  3. Irfan BaigDecember 6, 2007 ב 09:53

    Hi

    What about using resources in code? Can you provide any samples?

    Reply
  4. Tomer ShamamDecember 6, 2007 ב 18:08

    Hi,

    You should bind your properties to the dictionary from code:

    Binding binding = new Binding(“Dictionary”);
    binding.Source = LanguageContext.Instance;
    LanguageConverter converter = new LanguageConverter(uid, vid);
    binding.Converter = converter;
    BindingOperations.SetBinding(yourTargetObject, yourTargetProperty, binding);

    You can wrap this code in a class, lets say: LanguageBinder, to prevent writing this code over and over again.

    Tomer

    Reply
  5. EvgeniMarch 31, 2008 ב 07:12

    Tomer Shalom!
    Nice work you did here, I really like this approach. I have one question regarding implementation: when I passed over your code, I saw you using two way binding (when you creating binding inside markup extension). Well, I expected to use one way binding, because I interesting to monitor only source changes. However, when I changed to one way, it stopped to work. In addition, I saw that no one anymore subscribing to “PropertyChanged” event of the dictionary. Could you please explain this point? (why one way binding is not works here).

    Thanks,
    Evgeni

    Reply
  6. XaviJuly 23, 2008 ב 11:30

    There is a bug in your code:

    If you are using a DataTemplate in a UserControl, in TranslateExtension.ProvideValue method, you receive a System.Windows.SharedDp object in service.TargetProperty. As you make a cast to DependencyObject type, it returns “this”, so translation is not made.

    Reply
  7. Tomer ShamamJuly 23, 2008 ב 11:43

    Hi Xavi,

    As I already wrote: “the code should be refactored for production. Use it on your own risk”

    This means that you may find one or more BUGS in my code.

    I only gave the concept.

    Cheers

    Reply
  8. XaviJuly 23, 2008 ב 11:53

    Maybe you have missunderstood me. My purpose was improving your great idea and learning how to fix the bug I found.

    I didn’t know you were only interested in giving th concept, not tracking its feedback.

    Thanks anyway.

    Reply
  9. Tomer ShamamJuly 23, 2008 ב 12:14

    Thanks for finding a BUG. It will help others to know about it and fix it.

    Reply
  10. BennyJuly 24, 2008 ב 12:36

    Tomer,

    What if you want to localize an error message that is generated in code?

    Reply
  11. Tomer ShamamJuly 24, 2008 ב 15:20

    LanguageDictionary dictionary = LanguageDictionary.GetDictionary(

      LanguageContext.Instance.Culture);

    string message = dictionary.Translate(

      “NotEnoughMemory”, “Message”, typeof(string)) as string;

     

    English Entry:

    <Value Id=”NotEnoughMemory” Message=”There is not enough memory” />

    Hebrew Entry:

    <Value Id=”NotEnoughMemory” Message=”אין מספיק זכרון” />

    Cheers

    Reply
  12. Tomer ShamamJuly 24, 2008 ב 15:40

    I have also updated the code to version 1.1. Now you can use a simple form to get any message from code:

    string message = LanguageDictionary.Current.Translate(“AnyKey”, “AnyMessage”);

    Hope this helps.

    Reply
  13. SeriousMJuly 26, 2008 ב 18:52

    hey, please have a look at my engine! i dont have these problems andmore and it works faster tham yours :)

    http://www.codeplex.com/WPFLocalizeExtension

    Reply
  14. inTaggerJuly 30, 2008 ב 10:21

    Hail Tomer Shamam! The great miss of your solution (may be of Blend && VS) is impossibility of editing UI in Blend and VS designers (exception is thrown).
    Look at my solution http://intagger.blogspot.com/2008/07/wpf-application-localization-pattern_29.html i think it’s simpler and allow seeing all localized values in Blend or VS designers.

    Reply
  15. Tomer ShamamJuly 30, 2008 ב 11:41

    Hi inTagger,

    Nice solution, but the great miss of your solution may be passing arguments for translation. For example: “You have {0} new message!”, where {0} can be retrieved via binding.

    Also you didn’t provide an option to replace the dictionary source. It should be always XAML based ResourceDictionary.

    Reply
  16. Tomer ShamamAugust 12, 2008 ב 17:51

    Hi inTagger again,

    “The great miss of your solution (may be of Blend && VS) is impossibility of editing UI in Blend and VS designers (exception is thrown)”

    This bug was fixed, so there isn’t “a great miss” anymore.

    Thanks

    Reply
  17. Rob BurkeSeptember 2, 2008 ב 17:08

    Thanks for this Tomer. With a few additions to this technique for convenience, I’ve found this really useful on a project. Great work! Please do keep updating this if you take any feedback on board. Do you still use this library, or some riff on this, for your WPF localization needs?

    Reply
  18. CelsoSeptember 5, 2008 ב 11:32

    Hi. Thanks for this solution it´s great. But I will like to if somebody has the following problem. I have some controls on a Grid that are using a dynamic Uid, and I will like to linked its disposition at the Grid with this localization solution. So I used the following code:
















    When I have the definition for the Row and Colum elements on the Dictionary it works fine. But when the vid property is not contain the default resulting value is not working so all the elements are being positioned at the column and row 0 of the Grid. So I will like to know what I´m doing wrong or why is this happening.

    Reply
  19. Tomer ShamamSeptember 7, 2008 ב 14:42

    Hi Rob,

    Yes I’m using this solution in production. Also I know others that are using it.

    Enjoy.

    Reply
  20. Tomer ShamamSeptember 7, 2008 ב 14:48

    Hi Celso,

    I would really like to help but I didn’t understand what you want to do, also why you are using dynamic uid’s?

    Thanks

    Reply
  21. BenOctober 11, 2008 ב 17:57

    Hi, Tomer

    I’m trying to localize a contextmenu without any success. The code that I’m curently using is the following:




    After making some research I found out that the problem with this code is the fact that a contextmenu lacks of context inheritance, such as explained in this post http://blogs.msdn.com/nickkramer/archive/2006/08/18/705116.aspx. So the menuitem doesn’t really knows that there is a Root element to associate the uid binding.

    Do you have any suggestion in order to implement this?

    Thanks

    Reply
  22. Tomer ShamamOctober 23, 2008 ב 21:41

    Hi Ben,

    Yes you are right. ContextMenu is not part of the visual tree, hence it is not supported by my solution. Maybe I will add this special support in the future. Right now, you can Bind directly to the dictionary. See what the markup extension does.

    Reply
  23. JurmerianNovember 4, 2008 ב 12:49

    It lacks tooltip support.

    Reply
  24. Mauro CeciliJanuary 21, 2009 ב 16:51

    How can i use your solution in a listView – GridViewColumn.CellTemplate with triggers? this is my case:








    I try to use Loc:Translate.Uid=”ChooseCardStateScaduta” setter tag but is worong so i tried in XAML is correct but at run time i have only the default value. Can you help me?

    Thnaks for your great job!!

    Reply
  25. MarcelJanuary 26, 2009 ב 21:25

    Hi Tommer, thanks for your code, I’m a .Net developer starting in WPF. I used my own code based on yours so I can use my own implementation of the Dictionary.

    I think your code has a great concept, wich I want to discuss with you because your comments will be very helpful.

    I assume the on-the-fly update in the UI gets done because the source of the binding (in ProvideValue method of MarkupExtension inheritor) is the instance of the LanguageContext class, wich implements INotifyPropertyChanged so the users of this class be notified once it’s Culture property has changed.

    So my question is related with having a binding inside the content of some WPF control:

    Is that(2nd paragraph) the main point of UI updating on-the-fly when language selection changes??

    My question came because I used your code like this:

    <Label>

         <Label.Content>

               <Translator>

                     <Binding Path={SomeProperty}/>

               <Translator>

         </Label.Content>

    </Label>

    and it does not work, I mean, Label does not get notified when LanguageContext.Culture has changed.

    But also, like this:

    <Label>

         <Label.Content>

               <Translator Key=’SomeResourceKey’></Translator>

         </Label.Content>

    </Label>

    where ‘SomeResourceKey’ is a key in the resource file, it works perfect.

    Why the first sample is not working?

    thanks in advance

    Marcel

    Reply
  26. Nader AlkhatibFebruary 1, 2009 ב 05:55

    i think with the following the URL, the original MS solution is complete with no cons (the steps involved in localization can not be thought of as cons):
    http://www.codeproject.com/KB/WPF/LocBamlClickOnce.aspx

    Reply
  27. Tomer ShamamFebruary 6, 2009 ב 14:21

    Jurmerian, tooltips are natively supported, you just need to ask for it:

    ToolTip="{loc:Translate}"
    Text="{loc:Translate Default=Text 2}"
    Background="{loc:Translate AliceBlue}"
    Width="{loc:Translate 300}"
    Height="{loc:Translate 30}" FontSize="18">

    Reply
  28. Tomer ShamamFebruary 6, 2009 ב 14:35

    Hi Mauro Cecili and Marcel!

    Sorry for the late reply, I’ve upgraded the tool to version 1.3, and thanks to you I’ve added support for requesting any Uid decleratively.

    Now you can provide Uid as part of the Translate markup extension, this will overrides any other attached Uid.

    Using this feature, you can now write code like this:

    <DataTemplate DataType=”{x:Type local:Data}”>

      <DataTemplate.Triggers>

         <DataTrigger Binding=”{Binding Path=ID}” Value=”1″>

            <Setter Property=”ToolTip”

                    Value=”{loc:Translate Uid=11, Default=Apple}” />

         </DataTrigger>

         <DataTrigger Binding=”{Binding Path=ID}” Value=”2″>

     <Setter Property=”ToolTip”

                     Value=”{loc:Translate Uid=12, Default=Bannana}” />

         </DataTrigger>

         <DataTrigger Binding=”{Binding Path=ID}” Value=”3″>

             <Setter Property=”ToolTip”

                     Value=”{loc:Translate Uid=13, Default=Melon}” />

         </DataTrigger>

      </DataTemplate.Triggers>

    </DataTemplate>

    Cheers!

    Reply
  29. Abraham MathewMarch 10, 2009 ב 09:49

    Hi,
    Thank you for your code for localization.I am trying to port your demo code to a WPF Project we have to implement localization support for multiple languages,I have defined two xml files:

    1.en-US.xml
    2.ar-KW.xml

    ——————————————————————————–
    < ?xml version="1.0" encoding="utf-8"?>



    ——————————————————————————–
    < ?xml version="1.0" encoding="utf-8" ?>



    ——————————————————————————–

    Issue is that when I load the Arabic file from \Languages folder of my app the
    app freezes,but when I rename the en-US.xml file to ar-KW.xml file there is
    no issues (but english is shown).

    I just use the library to read the current value of the title and change
    it to the local value and fill a field name of a grid property.

    LanguageDictionary.RegisterDictionary(CultureInfo.GetCultureInfo(“ar-KW”), new XmlLanguageDictionary(“Languages/ar-KW.xml”));
    LanguageContext.Instance.Culture = CultureInfo.GetCultureInfo(“ar-KW”);

    try {
    column.Title = LanguageDictionary.Current.Translate(fieldLayoutInfo.Title, “Content”);
    } catch (Exception ex) {
    throw (ex);
    }

    Is there any encoding that I have to follow when I create my xml file ?

    Any suggestions would be helpfull

    Thanks

    Reply
  30. Tomer ShamamMarch 10, 2009 ב 22:52

    Hi Abraham,

    It looks like you have problems with XmlDocument and not WPF.

    Reply
  31. ArthurMay 18, 2009 ב 17:54

    Hello Tomer,

    First I would like to thank you for this usefull piece of code.

    Hope you could help me with the following problem;

    When I try to use it in a resource dictionary it works, but Blend 2.5 gives me the error “The ‘:’ characher, hexadecimalvalue 0x3A, cannot be included in a name. Line…”. Visual studio does not give this error. Also running the app is no problem.

    Reply
  32. Tomer ShamamMay 25, 2009 ב 08:54

    Arthur, did you try it on Blend 2.0 Or 3.0 preview?
    2.5 is kind of deprecated.

    Reply
  33. YuvalJune 3, 2009 ב 11:51

    Hi Tomer,
    I’m from AVT, and participated in your course on WPF.
    As you know, we use your localization module in our software.
    It’s been modified through time, but the basics are the same.

    There is that bug in version 1.0 where you cannot open the designer. You fixed it in version 1.2 and that’s great.
    We want to fix it in our code also.

    Unfortunatelly, I cannot spot the bug fix among all your changes for version 1.2.

    Can you help me with that? Can you refer me to the exact code change that fixes the bug?

    Thanks.

    Reply
  34. realmontanakidJune 3, 2009 ב 22:46

    Is it possible to pass a vid in xaml? What you did with “NotEnoughMemory” and “Message”. Could it work from Xaml code?

    Kind regards

    Reply
  35. Tomer ShamamJune 8, 2009 ב 09:10

    Hi Yuval,

    Unfortunately I don’t remember what the change was so please download version 1.1 and 1.2 and compare both using WinDiff for example.

    Reply
  36. AnonAugust 14, 2009 ב 02:33

    This sample project was just what I needed to enable my users to select a language on the fly. Seems to work well. Thanks for sharing your expertise, Tomer!

    Reply
  37. ArthurAugust 20, 2009 ב 13:45

    Hi Tomer,

    I try to have a TextBlock in a datatemplate translated. The TextBlock is not bound to any data. Whatever I try, I cannot get the textblock to translate.
    Then I started to modify your sample app to see if it would work there. I modified the DataTemplate (a stackpanel wuth the orgininal contents plus TextBlock loc:Translate.Uid=”20″ Foreground=”Green” Text=”{loc:Translate}”

    It al works fine, but when I start your MainWindow from another window (and make that window the startup window) it fails to translate this TextBlock as well.

    How to solve this??

    Best regards,

    Arthur

    Reply
  38. JohnSeptember 13, 2009 ב 16:21

    Which license applies to the files?

    Reply
  39. Tomer ShamamSeptember 13, 2009 ב 22:19

    Hi John,
    You may use it freely, just add credits.

    Thanks,
    Tomer

    Reply
  40. ChrisSeptember 20, 2009 ב 13:19

    Hallo everybody,
    I have problems with Blend 3. My Code looks like this: ….loc:Translate.Uid=”EntryWindow_Btn1_DataHandling” Text=”{loc:Translate Default=Type}”.
    This works, but if i try to edit the ‘Default = Type1′ -Property (in Blend 3), i get an Exception (.. for the Translate-Type, there is no DependencyObjectType (Default)-Element available. In the Code we have an normal Property Default(get;set) maybe this is the problem.
    Anybody has an idea.

    Thanks,
    Chris

    Reply
  41. YannickMay 18, 2010 ב 13:51

    Hi all,

    I’m trying to set a tooltip on a button in a DataGridTemplateColumn,

    I tried a lot but either it gives a runtime error or it uses the default value:
    something like this:





  42. YannickMay 18, 2010 ב 14:11

    handy addition

    Using direct translation returns null if the value is not found,
    therefore I added the following method in “LanguageDictionary.cs” so default values can be used:

    public TValue Translate(string uid, string vid, object defaultValue)
    {
    return (TValue)Translate(uid, vid, defaultValue, typeof(TValue));
    }

    Reply
  43. EdJuly 21, 2010 ב 15:57

    Hi Tomer, thank you for taking the time to do this. It looks precisely like the solution I need to implement and so could save me hours.

    Reply
  44. Tomer ShamamJuly 22, 2010 ב 00:09

    You welcome Ed, it’s my pleasure.

    Reply
  45. IsaacAugust 25, 2010 ב 01:05

    Big thanks for the approach , VERY COOOOOOOL !!!! Like it !!! And will use it for each of my projects

    Reply
  46. SaraganiNovember 1, 2010 ב 15:53

    Hi, it will be good if I can address a specific property in the XML (Right now it goes to the property with the name like the property in the Control).

    This is good when the same text is used in several places, for example a TextBlock and a Lable (while one of them has Text and the other Content).

    The Dynamic Uid idea is good but to excellent.
    I was hoping to be able to do:

    but GetUid(_target) returns an empty string.

    Why would I want to do that?? Because a user control that is Binded to a DataContext (in this case a ViewModel) have several properties to show in the UI, for example:



    You might suggest the idea of having the dynamic Uid in the template and having the properties in the XML, but if I need that each of the textboxes and the buttons will also have a tooltip then it makes the XML huge.

    Any way to achieve what I want?

    Reply
  47. dklJanuary 14, 2011 ב 10:22

    When you use this in .NET 4.0, you are likely to encounter weird exceptions when using both styles of setting Uid (attached property and inside markup extension) at the same time.

    Solution is simple: Rename one of the properties. In .NET 4.0 it is not possible to have regular property and dependency property of the same name.

    Reply
    1. JacquesOctober 14, 2014 ב 16:20

      Not sure if it will help you or not, but I was able to get it working with .NET 4

      I changed the Localization library to .NET 4 and started a new WPF app, referencing the library. I only used the basic syntax and it works, which is good enough for me for now:

      This also works:

      I suspect it’s some of the other advanced features that are broken in the demo, because mine is also still broken.

      And if i change to the following synatx it breaks as you described:

      Reply
    2. JacquesOctober 15, 2014 ב 15:11

      Thanks for that!

      I had converted it to .NET 4 and encountered a situation where I needed to mix the styles of setting the uid due to it mysteriously blank in a certain case. Renaming the one to Key solved the issue

      Reply
  48. drfJanuary 18, 2011 ב 18:31

    dkl, I’m not sure I understand. Which properties have the same name?
    Thanks.

    Reply
  49. Exception HandlerMay 3, 2011 ב 12:06

    Thanks for your post.. I have done the same in my project I have added reference of your dll file, and have created the same folder Languages and inside this I have two xml files..
    when i run the application its save file not found…
    can you please explain what i am doing wrong here,,

    Regards

    Reply
  50. Tomer ShamamMay 3, 2011 ב 22:25

    Hi Exception Handler,

    If you could send me a prototype of your project, I’ll be happy to fix this for you.

    Tomer

    Reply
  51. Exception HandlerMay 4, 2011 ב 11:23

    I have added a value for tooltip and text in en-US.xml as follow

    and then this applies on the below code,. The ToolTip is not working here
    but the Image Source is working,, means it is getting imagepath
    from en-US.xml

    What I am doing wrong here

    IsReadOnly="True" Selector.IsSelected="True" >




    loc:Translate.Uid="8" Source="{loc:Translate Source}">



    Regards

    Reply
  52. Exception HandlerMay 4, 2011 ב 11:25

    I have added a value for tooltip and text in en-US.xml as follow

    and then this applies on the below code,. The ToolTip and Text is not
    working here ,, means not getting values from en-US.xml

    but the Image Source is working,, means it is getting imagepath
    from en-US.xml

    What I am doing wrong here

    IsReadOnly="True" Selector.IsSelected="True" >




    loc:Translate.Uid="8" Source="{loc:Translate Source}">



    Regards

    Reply
  53. Tomer ShamamMay 4, 2011 ב 21:10

    Hi Exception Handler,

    You provided two versions of your code, so I’ll try to target the latest one.

    First you don’t need to write ToolTip=”{loc:Translate ToolTip}”, just write ToolTip=”{loc:Translate}”. The markup extension already knows that it activated on the ToolTip property.

    Second, in the Text=”{loc:Translate Text }, it looks like you’ve added additional space after “Text”, try to remove it since XAML parser think that it should be ‘Text ‘.

    As for the tooltip, have no clue. Did you spell it correctly?

    Feel free to send me a code sample.

    Tomer

    Reply
  54. efMay 10, 2011 ב 22:04

    Hi,

    Great solution!
    How can I pass arguments to {0} and {1} etc when I bind programmatically?
    I bind like this, to add text to a button:
    Binding binding = new Binding(“Dictionary”);
    binding.Source = LanguageContext.Instance;
    binding.Converter = new LanguageConverter(uid, “Text”, text);
    BindingOperations.SetBinding(aButton, Button.ContentProperty, binding);

    Any help is much appreciated!

    Regards

    Reply
  55. Exception Handler,May 15, 2011 ב 17:07

    I have a WPF brower base Application.The file not found error on

    LanguageDictionary.RegisterDictionary(
    CultureInfo.GetCultureInfo(“en-US”),
    new XmlLanguageDictionary(“Languages/en-US.xml”));

    .. How can i email you my source code ?

    Reply
  56. Fabio AlvesMay 16, 2011 ב 16:54

    Blend gives me an error:
    The name “Translate” does not exist in the namespace “http://schemas.tomer.com/winfx/2006/xaml/presentation”.

    Is there a new website hosting the xml schema?

    Reply
  57. Tomer ShamamMay 18, 2011 ב 11:39

    Hi Fabio,

    What version of Blend are you using?
    this “schema” doesn’t related to the web at all. It’s just a unique string I defined as an assembly attribute “XmlnsNamespaceAttribute”, and it’s defined in the AssemblyInfo.cs file.

    Reply
  58. Tomer ShamamMay 18, 2011 ב 11:42

    Exception Handler, Hi again ;)
    Working with XBAP application, the runtime directory is different than a regular app (security reasons). In that case, you should have the dictionary XML in the working directory. You may also try working with Isolated Storage.

    Reply
  59. MelianosNovember 8, 2011 ב 17:48

    Hello, i’m using your translation mechanism, all works fine but i strongly need one improvement which is : allow uid to accept binding so that i can bind to some values when using mvvm.

    e.g : {{loc:Translate Uid={Binding Path=ViewModelProperty}}

    Any help on this ?

    Thx

    Thanks

    Reply
  60. OneOfYourFanMay 29, 2012 ב 17:17

    Hi Tomer!
    I’m delighted with your solution. I tried recompiling the Localization project and Demo application using .NET 4 Client Profile and when I run it, I get a run-time error: “Object of type ‘Tomers.WPF.Localization.Translate’ cannot be converted to type ‘System.Windows.DependencyObject’.”.
    I pinpointed the problem to the setter in the Data Trigger:

    Any idea what can be the fix? The Translate class already inherits MarkupExtension, so it cannot also derive from DependencyObject…

    Reply
  61. OneOfYourFanMay 29, 2012 ב 17:24

    Hi Tomer!

    I’m delighted with solution except for one point. I tried recompiling it with .NET 4 Client Profile and it gave me run-time argument exception: “Object of type ‘Tomers.WPF.Localization.Translate’ cannot be converted to type ‘System.Windows.DependencyObject’.”.

    The line in cause is:

    I don’t see how to solve the issue as the Translate class already inherits class MarkupExtension, so it cannot also derive from Dependency Object.

    Any help would be welcome.

    Reply
  62. Tomer ShamamJune 3, 2012 ב 00:32

    It’s something with the “Trigger” inside the DataTemplate.
    As I don’t have the time to investigate it, please remove the triggers, if you don’t use this solution inside a trigger:











    Reply
  63. ArthollOctober 25, 2012 ב 09:43

    Hi,
    I want to thank you for your extension. It’s great.
    I have one issue and it was really helpfull if you can help me.
    In my WPF app I set some text onto Slider buttons and I do this in App.xaml. Is there any possibility to translate this text? Because if I do this as normal it doesn’t work…
    Thank you for your advice.

    Reply
  64. GeertenDecember 6, 2012 ב 11:28

    Hi Tomer Shamam,

    Thanks for your solution, it’s very nice and elegant, I like it!

    However, while converting my XAML files to be localizable, I encountered an error.

    I have the following ItemsControl:









    But when I execute this, the software just hangs! And I can’t figure out what is causing it.

    If I change the button to:

    It shows the buttons, but without the content..

    Could you help with this?

    Thanks in advance.

    Best regards,
    Geerten

    Reply
  65. GeertenDecember 10, 2012 ב 10:36

    Oh, and btw:
    I renamed your Uid property to Key.

    Best regards,
    Geerten

    Reply
  66. YvesFebruary 6, 2013 ב 14:00

    Hi Tomer,

    could you please explain how this all works? I’m trying to do something similar and create a MarkupExtension that can translate arbitrary, specified text keys into strings, but also regards a specified counting value to use specialised translations. Like "one day" but "4 days". This count parameter must be bindable because it can change.

    I’ve tried to follow your code, starting in the TranslateExtension.BindDictionary method, but I can’t follow it anywhere. It returns a BindingExpression instance and that’s all, and it seems to work. Is it understandable what your code does and why it works? How did you find out about it all?

    I haven’t even tried to understand the MultiBinding part yet, though it looks interesting.

    Reply
  67. PragyaMarch 20, 2013 ב 11:00

    While loading german language file, we are getting following exception :

    ‘Schließen’ is an unexpected token. Expecting white space. Line 455, position 108.

    Following is the snippet from ‘de-DE.xml’

    <Value Id="msgExit" Text="Schließen" />

    The encoding of the xml is UTF-8 only.

    Requesting you suggest something for this problem.

    Reply
  68. NicolasSeptember 4, 2013 ב 04:18

    Hi Tomers

    I’ve migrated your project to .NET 4.5.
    XAML raises Exception

    System.Windows.Markup.XamlParseException was unhandled
    HResult=-2146233087
    Message=’Set property ‘Tomers.WPF.Localization.Translate.Uid’ threw an exception.’ Line number ’38′ and line position ’33′.
    Source=PresentationFramework
    LineNumber=38
    LinePosition=33
    StackTrace:
    at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
    at System.Windows.Markup.WpfXamlLoader.LoadDeferredContent(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings parentSettings, Uri baseUri)
    at System.Windows.ResourceDictionary.CreateObject(KeyRecord key)
    at System.Windows.ResourceDictionary.OnGettingValue(Object key, Object& value, Boolean& canCache)
    at System.Windows.ResourceDictionary.OnGettingValuePrivate(Object key, Object& value, Boolean& canCache)
    at System.Windows.ResourceDictionary.GetValueWithoutLock(Object key, Boolean& canCache)
    at System.Windows.ResourceDictionary.GetValue(Object key, Boolean& canCache)
    at System.Windows.FrameworkElement.FindBestMatchInResourceDictionary(ResourceDictionary table, ArrayList keys, Int32 exactMatch, Int32& bestMatch)
    at System.Windows.FrameworkElement.FindTemplateResourceInTree(DependencyObject target, ArrayList keys, Int32 exactMatch, Int32& bestMatch)
    at System.Windows.FrameworkElement.FindTemplateResourceInternal(DependencyObject target, Object item, Type templateType)
    at System.Windows.Controls.ContentPresenter.DefaultSelector.SelectTemplate(Object item, DependencyObject container)
    at System.Windows.Controls.ContentPresenter.ChooseTemplate()
    at System.Windows.Controls.ContentPresenter.EnsureTemplate()
    at System.Windows.Controls.ContentPresenter.OnPreApplyTemplate()
    at System.Windows.FrameworkElement.ApplyTemplate()
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Controls.Control.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Controls.StackPanel.StackMeasureHelper(IStackMeasure measureElement, IStackMeasureScrollData scrollData, Size constraint)
    at System.Windows.Controls.StackPanel.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Controls.Border.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
    at System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Controls.Border.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Window.MeasureOverrideHelper(Size constraint)
    at System.Windows.Window.MeasureOverride(Size availableSize)
    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Interop.HwndSource.SetLayoutSize()
    at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
    at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
    at System.Windows.Window.SetRootVisual()
    at System.Windows.Window.SetRootVisualAndUpdateSTC()
    at System.Windows.Window.SetupInitialState(Double requestedTop, Double requestedLeft, Double requestedWidth, Double requestedHeight)
    at System.Windows.Window.CreateSourceWindow(Boolean duringShow)
    at System.Windows.Window.CreateSourceWindowDuringShow()
    at System.Windows.Window.SafeCreateWindowDuringShow()
    at System.Windows.Window.ShowHelper(Object booleanBox)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.DispatcherOperation.InvokeImpl()
    at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Windows.Threading.DispatcherOperation.Invoke()
    at System.Windows.Threading.Dispatcher.ProcessQueue()
    at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
    at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.Run()
    at System.Windows.Application.RunDispatcher(Object ignore)
    at System.Windows.Application.RunInternal(Window window)
    at System.Windows.Application.Run(Window window)
    at System.Windows.Application.Run()
    at Tomers.WPF.Localization.App.Main() in c:\Dev\c#\Tomers\LocalizationDemo\obj\Debug\App.g.cs:line 0
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
    at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()
    InnerException: System.ArgumentException
    HResult=-2147024809
    Message=Object of type ‘Tomers.WPF.Localization.Translate’ cannot be converted to type ‘System.Windows.DependencyObject’.
    Source=mscorlib
    StackTrace:
    at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
    at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
    at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
    at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
    at System.Xaml.Schema.SafeReflectionInvoker.InvokeMethodCritical(MethodInfo method, Object instance, Object[] args)
    at System.Xaml.Schema.SafeReflectionInvoker.InvokeMethod(MethodInfo method, Object instance, Object[] args)
    at System.Xaml.Schema.XamlMemberInvoker.SetValueSafeCritical(Object instance, Object value)
    at System.Xaml.Schema.XamlMemberInvoker.SetValue(Object instance, Object value)
    at System.Windows.Baml2006.WpfMemberInvoker.SetValue(Object instance, Object value)
    at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(XamlMember member, Object obj, Object value)
    at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(Object inst, XamlMember property, Object value)
    InnerException:

    Reply