views:

340

answers:

3

I have a small WPF application that I am working on localizing. I have read thru a number of documents but I have not found a good source of information about dealing with 'rich' content - for example:

I have a ui element (TextBlock) that contains a mix of text with formatting, inline images and symbols. I want to localize this element. I believe to localize this element in a way that is natural in another language that the position of the formulas/symbols will need to shift in relation to the surrounding text - and that the best formatting for the text may be slightly different in other languages as well.

I am looking for suggestions, resources and/or approaches for localizing 'rich' content (content that mixes text, formatting and inline ui elements) in XAML/WPF. Given the emphasis on composition and 'rich' UI in WPF I was surprised not to find any information on the scenario above - what am I missing?

I have thought about storing XAML in the resource file and then parsing it at runtime for inclusion in the UI and about creating a view/usercontrol that is swapped out based on locale - but I am not seeing any mention of these approaches (which makes me wonder if I am on the 'wrong track') and am hoping someone has experience or information to share?

Thanks!

A: 

Let's say you have a text:

"Hello, my <b>dear</b> user!"

And you want to localize that text into some other language:

"Привет, мой <b>дорогой</b> пользователь!!"

So, your localization framework should either store it as it is in one whole string with all formatting and rely on translators to be patient and hard-working and to preserve all formatting tags. Or if formatting is very important, then you should split the string into (3) parts and apply formatting when string is constructed and store each part as individual entry in translations store.

EDIT: If your rich-text contains an image reference, then you should localize the way link to image is produced with something like this:

<img src="$current_locale/logo.jpg" />

or even have a function that will return a default image location if image for current locale is missing:

<img src="$get_current_locale_or_default_locale_image(logo.jpg)" />
Superfilin
I understand this example for html -> but for xaml I don't think that it is as simple as storing the tags. I know that runtime xaml parsing is an option - if you have experience with the suggestion above in xaml could you elaborate? Thanks!
I have no direct experience with WPF or XAML, but as far as I understand XAML defines your UI elements and inside those UI elements you have some texts (rich (= with some formatting) or plain), and I guess you want to localize those texts, not the UI definition, right?. If yes, then I think it does not really matter for localization engine if you localize formatted text or plain text as it is just same old key/(formatted or plain)value pair.
Superfilin
Here is the link that I guess you also read: http://msdn.microsoft.com/en-us/library/ms788718.aspx#workflow_to_localize.
Superfilin
Because the ui element to be localized mixes formatting/text and inline vector/XAML drawings I suppose that I do want to localize the UI definition. I appreciate your examples above and I think they are helping me clarify my question - but the when/how of XAML parsing in a typical WPF app is different enough from HTML that I am looking specifically for WPF/XAML suggestions/help.
I have added some tags to your question probably others with XAML experience to find it. Probably if you provide a specific example in your question formulation you'll get more specific advice as well.
Superfilin
In any case I think you should split the way textual graphical information are localized. Possibly by storing XAML vector graphics definition externally (like you would do with binary images) from your XAML UI layout.
Superfilin
correction to previous comment: "textual _and_ graphical"
Superfilin
A: 

The StaticResource markup extension works very well for what you are trying to accomplish. You can include almost anything using a StaticResource, even when a DynamicResource doesn't work:

<Window ...>
  <Window.Resources>

    <Span x:Key="Whatever">
      <Bold>Hello</Bold> there<LineBreak/>
      A green circle:
      <InlineUIContainer>
        <Ellipse Width="10" Height="10" Fill="Green" />
      </InlineUIContainer>
    </Inline>

  </Window.Resources>

  ...
  <TextBlock>
    <StaticResource ResourceKey="Whatever" />
  </TextBlock>

Now localizing this is easy:

  1. Move the Span resource into a separate ResourceDictionary and merge into your Application's resource dictionary.
  2. During application startup and before any windows are created, use code to get the current culture and add an additional language-specific ResourceDictionary to application.Resources.MergedDictionaries. The satellite dictionary can be loaded using WPF's built-in localization mechanism.

As long as the dictionares are merged into the application dictionary in the correct order, any named resource that is found in the localized dictioary will take precendence over the one in the main dictionary, for example your Spanish localization dll could have a xaml file containing this:

<ResourceDictionary>
  <Span x:Key="Whatever">
    El círculo rojo
      <InlineUIContainer>
        <Ellipse Width="10" Height="10" Fill="Red" />
      </InlineUIContainer>
    dice <Bold>hola!</Bold>
  </Span>
</ResourceDictionary>

Note that the message is similar, but for Spanish the circle is red and the layout of the text is different.

You can take this much further with ControlTemplates if you want to. Using ControlTemplates will allow you to do such things as have buttons laid out in a different order depending on the locale. For example if your generic dictionary contains:

<ResourceDictionary>
  <ControlTempate x:Key="Something" TargetType="ContentControl">
    <StackPanel>
      <TextBlock Text="In English we want the text above the button" />
      <ContentPresenter />
    </StackPanel>
  </ControlTemplate>
</ResourceDictionary>

You can add this to your window or user control:

<ContentControl Template="{StaticResource Something}">
  <Button Command="Save">Save File</Button>
</ContentControl>

And then you change the layout for another language, for example:

<ResourceDictionary>
  <ControlTempate x:Key="Something" TargetType="ContentControl">
    <DockPanel>
      <ContentPresenter DockPanel.Dock="Left" />
      <TextBlock Text="En español en el botón a la izquierda del texto" />
        <!-- In spanish the button is to the left of the text -->
    </DockPanel>
  </ControlTemplate>
</ResourceDictionary>

Note: If you only use localization in DependencyProperties (eg no InlineCollections, etc) you can get away with using {DynamicResource} which allows the locale to change at any time with an instant update of the UI. To do this with my first example, instead of including a <Span> in the ResourceDictionary and including it inside a TextBlock, you can put the TextBlock in a ControlTemplate inside the ResourceDictionary.

This is only the beginning of the flexibility of localization with WPF. You can go much further.

Ray Burns
A: 

I marked the answer by Ray Burns using the StaticResource extension as the accepted answer, I think alot of the details about that approach are perfect. I also wanted to show another idea - using T4 templates to generate 'loose' xaml files that are included in the output and parsed at runtime based on the CurrentCulture.

The code below is the contents of the file base_block.tt. One detail that is important below is the use of encoding="Unicode" (I assumed Utf-8 would work but the XamlParser would error on some characters when the template specified Utf-8, apparently because of the BOM setting).

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".xaml" encoding="Unicode"#>

<UserControl
    xml:lang="<#= this.xml_lang #>"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="basic_styles.xaml" />
                <ResourceDictionary Source="equations.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>

        <WrapPanel Style="{StaticResource description_wrap_panel_style}">

                <TextBlock  x:Name="c_textblock"
                                Style="{StaticResource description_textblock_style}" 
                                AutomationProperties.Name = "<#= this.textblock_automation_name #>">
                        <#= this.textblock_constant_C_contents #>
                </TextBlock>

                <TextBlock  Style="{StaticResource description_textblock_style}"
                                KeyboardNavigation.TabIndex="1">
                        <#= this.hyperlink_textblock_contents #>
                </TextBlock>

                <TextBox    Style="{StaticResource entry_textbox_style}"
                                AutomationProperties.LabeledBy="{Binding ElementName=c_textblock}"
                                KeyboardNavigation.TabIndex="0">
                </TextBox>

        </WrapPanel>

</UserControl>

<#+ 

private string xml_lang = @"";
private string textblock_constant_C_contents = @"";
private string textblock_automation_name = @"";
private string hyperlink_textblock_contents = @"";

#>

base_block.tt can be used in an include in a .tt file that specifies the needed values - for English here is my en.tt file which will generate en.xaml:

<# 

xml_lang = @"en-US";

textblock_constant_C_contents = 
    @"Enter a constant, <Italic>C</Italic>, that satisfies
            <InlineUIContainer Style='{StaticResource image_container_style}'>
                    <Image x:Name='formula_11' Source='{StaticResource equation_11}' Style='{StaticResource image_style}' Tag='3.0'>
                    <Image.Height>
                        <MultiBinding Converter='{StaticResource image_size}'>
                            <Binding Mode='OneWay' ElementName='formula_11' Path='Tag'/>                        
                            <Binding Mode='OneWay' ElementName='c_textblock' Path='FontSize'/>
                        </MultiBinding>
                    </Image.Height>
                </Image>
            </InlineUIContainer>";

textblock_automation_name = @"Enter a Constant, C, that satisfies the following equation: the standard error of the estimate is equal to the constant C over the square root of the sample size";

hyperlink_textblock_contents = @"(<Hyperlink AutomationProperties.Name='More information about the constant C' 
                                        x:Name='c_hyperlink'>more info</Hyperlink>)";

#>

<#@ include file="base_block.tt" #>

Or for French - fr.tt -> fr.xaml:

<# 

xml_lang = @"fr";

textblock_constant_C_contents = 
    @"Entrez une constante, <Italic>C</Italic>, pour satisfaire
            <InlineUIContainer Style='{StaticResource image_container_style}'>
                    <Image x:Name='formula_11' Source='{StaticResource equation_11}' Style='{StaticResource image_style}' Tag='3.0'>
                    <Image.Height>
                        <MultiBinding Converter='{StaticResource image_size}'>
                            <Binding Mode='OneWay' ElementName='formula_11' Path='Tag'/>                        
                            <Binding Mode='OneWay' ElementName='c_textblock' Path='FontSize'/>
                        </MultiBinding>
                    </Image.Height>
                </Image>
            </InlineUIContainer>";

textblock_automation_name = @"Entrez une constante, C, qui satisfait l'équation suivante: l'erreur-type de l'estimation est égale à la constante C sur la racine carrée de la taille de l'échantillon.";

hyperlink_textblock_contents = @"(<Hyperlink AutomationProperties.Name=""Plus d'informations sur la constante C"">en savoir plus</Hyperlink>)";

#>

<#@ include file="base_block.tt" #>

The French above generates the following .xaml file:

<UserControl
    xml:lang="fr"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="basic_styles.xaml" />
                <ResourceDictionary Source="equations.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>

        <WrapPanel Style="{StaticResource description_wrap_panel_style}">

                <TextBlock  x:Name="c_textblock"
                            Style="{StaticResource description_textblock_style}" 
                            AutomationProperties.Name = "Entrez une constante, C, qui satisfait l'équation suivante: l'erreur-type de l'estimation est égale à la constante C sur la racine carrée de la taille de l'échantillon.">
                        Entrez une constante, <Italic>C</Italic>, pour satisfaire
                    <InlineUIContainer Style='{StaticResource image_container_style}'>
                            <Image x:Name='formula_11' Source='{StaticResource equation_11}' Style='{StaticResource image_style}' Tag='3.0'>
                            <Image.Height>
                                <MultiBinding Converter='{StaticResource image_size}'>
                                    <Binding Mode='OneWay' ElementName='formula_11' Path='Tag'/>                        
                                    <Binding Mode='OneWay' ElementName='c_textblock' Path='FontSize'/>
                                </MultiBinding>
                            </Image.Height>
                        </Image>
                    </InlineUIContainer>
                </TextBlock>

                <TextBlock  Style="{StaticResource description_textblock_style}"
                            KeyboardNavigation.TabIndex="1">
                        (<Hyperlink AutomationProperties.Name="Plus d'informations sur la constante C">en savoir plus</Hyperlink>)
                </TextBlock>

                <TextBox    Style="{StaticResource entry_textbox_style}"
                            AutomationProperties.LabeledBy="{Binding ElementName=c_textblock}"
                            KeyboardNavigation.TabIndex="0">
                </TextBox>

        </WrapPanel>
</UserControl>

At runtime I look at the CurrentCulture, compare that to the generated xaml files that are available, feed the file to XamlReader.Load() and add the resulting Usercontrol in where needed. A small sample app demostrating this is a available here.