WPF.Translations.TranslationBinding 1.0.1

dotnet add package WPF.Translations.TranslationBinding --version 1.0.1
NuGet\Install-Package WPF.Translations.TranslationBinding -Version 1.0.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WPF.Translations.TranslationBinding" Version="1.0.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add WPF.Translations.TranslationBinding --version 1.0.1
#r "nuget: WPF.Translations.TranslationBinding, 1.0.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install WPF.Translations.TranslationBinding as a Cake Addin
#addin nuget:?package=WPF.Translations.TranslationBinding&version=1.0.1

// Install WPF.Translations.TranslationBinding as a Cake Tool
#tool nuget:?package=WPF.Translations.TranslationBinding&version=1.0.1

WPF.Translations.TranslationBinding

A {TranslationBinding} for XAML.

Do Not Mix

My other translations API WPF.Translations, Nuget package here, and this API should not be used together. You should choose one or the other to manage your translations. Both will meet your needs whatever your situation but are used differently.

Basic Usage

This is not a MS package natively included in WPF so after you add the Nuget package reference to your project/solution, you'll need to add a namespace declaration at the top of your XAML file...

xmlns:tb="clr-namespace:WPF.Translations.TranslationBinding;assembly=WPF.Translations.TranslationBinding"

Usage will be very similar to a normal binding...

{tb:TranslationBinding TranslationKey=MyKey, FallbackValue=Fallback value for the translation binding.}

Parameters are supported, more on that below.

Requirements For Usage

There are a few things to note about the functionality of the API...

  • CultureInfo.DefaultThreadCurrentCulture or CultureInfo.DefaultThreadCurrentUICulture are used to track whether or not the culture changes. Use CultureInfo.DefaultThreadCurrentCulture if TranslationBindingOperations.UseUICulture = false and use CultureInfo.DefaultThreadCurrentUICulture if TranslationBindingOperations.UseUICulture = true. The developer is responsible for setting the CultureInfo property important to them, or both.
  • ITranslationProvider is required to be implemented by the developer. The interface implementation should be in the WPF application that has the need for translations. It should not be in satelite assemblies. There should be only one, meaning satelite assemblies should not all implement ITranslationProvider...even if they have their own translation needs. That being said satelite assemblies can have their own translations. More in the examples section. Additional note, the API will throw an InvalidOperationException if a ITranslationProvider is not set on the TranslationBindingOperations.TranslationProvider property before the first translation request is made.

Notes About Usage

  • Clean up of old translation bindings happens when the TranslationBindingOperation.CultureInfo event is fired or the developer calls TranslationBindingOperation.CleanUpOldBindings method. When changing languages the API will clean up old bindings so they can be garbage collected. If this needs to be forced then the developer can just call TranslationBindingOperation.CleanUpOldBindings.
    • For example, if you open a Window that has TranslationBindings in it and then close that window, those TranslationBindings will sit in memory until clean up occurs.
  • A TranslationBinding cannot be used in a Setter of a Style in XAML. So for example...
<Style x:Key="TextBlockPropertyUsage" TargetType="TextBlock">
    <Setter Property="Text" Value="{tb:TranslationBinding TranslationKey=Test, FallbackValue=Test fallback}" />
</Style>
  • An error reading something like; "TranslationBindingExtension is not valid for Setter.Value. The only supported MarkupExtension types are DynamicResourceExtension and BindingBase or derived types."
    • A TranslationBinding can only be set on a DependencyObject. So set it on the instance of the TextBlock using that style. That being said, a TranslationBinding can be used in a ControlTemplate that is used for a Template in a Setter. See example project.
    • If you need this kind of functionality then I suggest checking out my other translation API mentioned above.
  • Translations are a runtime thing not a design time thing. So if you want to see something in the designer then enter a FallbackValue.
  • If the developer has the need to use a translation before the XAML processor/renderer processes the first TranslationBinding then the developer will have to call TranslationBindingOperations.ReadInTranslationsForCulture() manually for the API to read the translation. Call this after setting CultureInfo.DefaultThreadCurrentCulture or CultureInfo.DefaultThreadCurrentUICulture or both. The API will read in translations when the XAML processor/renderer processes the first instance of a TranslationBinding.

It all sounds kind of complicated but it is really not that complicated.

Parameters

The API supports up to 3 parameters for translation bindings. Set the Parameter, Parameter2 or Parameter3 properties as needed. The Parameter properties have to be a binding in XAML.

<TextBlock VerticalAlignment="Top"
           Text="{tb:TranslationBinding TranslationKey=Testing, FallbackValue=Testing parameters, 
                                        Parameter={Binding Version}}" />

It is suggested to not listen to the TextChanged, or similar, on controls using a TranslationBinding that has a Parameter property assigned. The bound property will change multiple times while evaluating Parameter bindings.

If something more complex is needed then it is up to the developer to retrieve the string manually and perform the custom format work then.

From Code

Translations can be pulled in code, after they are read in, by calling TranslationBindingOperations.GetTranslation(string key). If the key is not found then null is returned.

Examples

Be sure to check out the testing WPF application project in the repository for code examples. We will cover some examples at a high level.

Implementing ITranslationProvider

Make a simple translation provider that reads XAML resource dictionaries for translations...

using WPF.Translations.TranslationBinding;

internal class TranslationProvider : ITranslationProvider
{
    public IDictionary<string, string> GetTranslationsForCulture(string culture)
    {
        Dictionary<string, string> translations = new Dictionary<string, string>();

        try
        {
            // we have multiple translation data sources
            // we will go through the main applications translations first
            string translationXaml = $"pack://application:,,,/Translations/Translations.{culture}.xaml";

            ReadInTranslations(translationXaml, translations);
        }
        catch (Exception ex)
        {
            ServiceLocator.Instance.Logger.Error($"An error occurred attempting to load translations.{Environment.NewLine}{ex}");
        }

        try
        {
            // next get translations from satelite assemblies
            string translationXaml = $"pack://application:,,,/CustomControlTesting;component/Translations/Translations.{culture}.xaml";

            ReadInTranslations(translationXaml, translations);
        }
        catch (Exception ex)
        {
            ServiceLocator.Instance.Logger.Error($"An error occurred attempting to load translations from satelite assembly: CustomControlTesting.{Environment.NewLine}{ex}");
        }

        return translations;
    }

    private void ReadInTranslations(string resource, Dictionary<string, string> translations)
    {
        ResourceDictionary resourceDictionary = new ResourceDictionary();
        resourceDictionary.Source = new Uri(resource);

        foreach (string key in resourceDictionary.Keys)
        {
            // no duplicate keys
            if (translations.ContainsKey(key)) continue;

            translations.Add(key, resourceDictionary[key].ToString());
        }
    }
}

As you can see from the simple example, we are reading translations from our main entry assembly/application and from satelite assemblies. This is because my XAML resouce dictionaries are marked as "Resource" under properties and so I can use pack application strings to access them. Please look up WPF pack URIs if you don't know and would like to know more.

This is amazing because now all those de, en, fr, it, zh-Hans, zh-Hant directories that have to get deployed with your applicaton can die!!!!!!!!!!! Translations can now be included natively in your application. This makes it so customers can't ruin your software by deleting the "de" directory and wonder why when they select German for the application it defaults back to English. 😐 Seriously... ... ...

Application StartUp Event in App.xaml.cs

Subscribe to the StartUp event in App.xaml and set your TranslationProvider...

TranslationBindingOperations.TranslationProvider = new TranslationProvider();

Next deciding on whether or not you want to set the CultureInfo.DefaultThreadCurrentCulture or the CultureInfo.DefaultThreadCurrentUICulture or both. If you are going to set both then this point is moot, but if you are not setting both then this point matters. If you want to manage the UI thread culture then set TranslationBindingOperations.UseUICulture = true and set CultureInfo.DefaultThreadCurrentUICulture. If you want to manage the thread culture then set CultureInfo.DefaultThreadCurrentCulture and leave the default false value for TranslationBindingOperations.UseUICulture.

After that you also need to decide to handle the TranslationBindingOperations.CultureChange call yourself by calling TranslationBindingOperations.RefreshTranslations() or by setting TranslationBindingOperations.RefreshAutomatically to true. If using the automatic route, there is a timer that fires every half a second and if a culture changed is detected then the TranslationBindingOperations.CultureChange event is fired. So there may be a small delay when changing the culture with the automatic timer.

Reminder that if you need to potentially show translations before any XAML is processed then remember to call TranslationBindingOperations.ReadInTranslationsForCulture() after setting CultureInfo.DefaultThreadCurrentCulture or CultureInfo.DefaultThreadCurrentUICulture or both. Then call TranslationBindingOperations.GetTranslation(string key) with the appropriate key.

Multiple Translation Sources

image

As you can see from the screen grab, it has also been mentioned already but you can have multiple translation data sources like this. It just depends on how you implement your translations. The example above along with the testing project in the repository demonstrate how to achieve this affect.

ITranslationProvider Power

There is no default implementation of the ITranslationProvider so it is up to you to tell the API what your translations are. You can use a XAML ResourceDictionary marked as a Resource and use pack application strings to load translations (this is what we do). You can have your XAML ResourceDictionaries copied to the hard drive and read off the HDD...though that is less cool. You could read text files, XML files and even connect to a database to pull in your translations. We just need you to return a Dictionary<string, string> (translation-key, translation) containing the translations...we don't care how you get them. 😃

XAML Examples

Basic Examples

<TextBlock Grid.Row="0" Style="{StaticResource TextBlockForegroundStyle}" VerticalAlignment="Top"
           Text="{tb:TranslationBinding TranslationKey=LogFileNotExistsMessage, FallbackValue=Log file message}" />
<TextBlock Grid.Row="1" Margin="0,0,0,0" Style="{StaticResource TextBlockForegroundStyle}" VerticalAlignment="Top"
           Text="{tb:TranslationBinding TranslationKey=Testing, FallbackValue=Testing parameters, 
                                        Parameter={Binding Version}}" />

Look-less Control Template

<Style TargetType="{x:Type local:TranslationBindingCustomControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:TranslationBindingCustomControl}">
                <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
                    <TextBlock Foreground="White" Text="{tb:TranslationBinding TranslationKey=CustomControlTest, FallbackValue=Custom control test fallback}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Nested ControlTemplates

<Style TargetType="{x:Type local:NestedTest}">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:NestedTest}">
                <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
                    <ToggleButton>
                        <ToggleButton.Style>
                            <Style TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
                                <Setter Property="Background" Value="Transparent" />
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate>
                                            <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                                                    BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
                                                <TextBlock Foreground="White" 
                                                           Text="{tb:TranslationBinding TranslationKey=CustomControlTest2, 
                                                                                        FallbackValue=Custom control test fallback 2}" />
                                            </Border>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ToggleButton.Style>
                    </ToggleButton>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Setter.Value Not Valid

As mentioned already you cannot do this...

<Style x:Key="TextBlockPropertyUsage" TargetType="TextBlock">
    <Setter Property="Text" Value="{tb:TranslationBinding TranslationKey=CustomControlTest, FallbackValue=Custom control test fallback 2}" />
</Style>

It will give you an error: "TranslationBindingExtension is not valid for Setter.Value. The only supported MarkupExtension types are DynamicResourceExtension and BindingBase or derived types."

Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0-windows7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.1 169 10/23/2023
1.0.0 108 10/22/2023