View Issue Details

IDProjectCategoryView StatusLast Update
0002710NoesisGUIUnitypublic2024-01-25 11:01
Reportersamc Assigned Tomaherne  
PrioritynormalSeveritymajor 
Status resolvedResolutionfixed 
Product Version3.2.1 
Target Version3.2.3Fixed in Version3.2.3 
Summary0002710: Unable to localize ControlTemplate or DataTemplate that live in a ResourceDictionary
Description

We're using the localization extension:

https://www.noesisengine.com/docs/App.ApplicationLauncher._LocExtension.html

To localize our game. Some of the text we need to localize lives in Resource Dictionaries. This includes ControlTemplate and DataTemplate.

It's not clear how to use the localize extension to localize the text in these templates.

Reference thread: https://www.noesisengine.com/forums/viewtopic.php?p=16133#p16133

PlatformWindows

Activities

sfernandez

sfernandez

2023-10-05 10:19

manager   ~0008791

The following patched file should fix the problem and allow using the Loc extension inside templates.
Could you give it a try?

LocExtension.cs (9,422 bytes)   
using Noesis;
using System;
using System.Collections.Generic;

namespace NoesisGUIExtensions
{
    /// <summary>
    /// Implements a markup extension that supports references to a localization ResourceDictionary.
    ///
    /// Provides a value for any XAML property attribute by looking up a reference in the
    /// ResourceDictionary defined by the Source attached property. Values will be re-evaluated when
    /// the Source attached property changes.
    ///
    /// If used with a string or object property, and the provided resource key is not found, the
    /// LocExtension will return a string in the format "<Loc !%s>" where %s is replaced with the key.
    ///
    /// This example shows the full setup for a LocExtension. It utilizes the RichText attached
    /// property to support BBCode markup in the localized strings. The Loc.Source property references
    /// the "Language_en-gb.xaml" ResourceDictionary below.
    ///
    /// Usage:
    ///
    ///    <StackPanel
    ///      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    ///      xmlns:noesis="clr-namespace:NoesisGUIExtensions"
    ///      noesis:Loc.Source="Language_en-gb.xaml">
    ///      <Image Source="{noesis:Loc Flag}"/>
    ///      <TextBlock noesis:RichText.Text="{noesis:Loc TitleLabel}"/>
    ///      <TextBlock noesis:RichText.Text="{noesis:Loc SoundLabel}"/>
    ///    </StackPanel>
    /// 
    /// This is the contents of a "Language_en-gb.xaml" localized ResourceDictionary:
    ///
    /// Usage:
    ///
    ///    <ResourceDictionary
    ///      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    ///      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    ///      xmlns:sys="clr-namespace:System;assembly=mscorlib">
    ///      <ImageBrush x:Key="Flag" ImageSource="Flag_en-gb.png" Stretch="Fill"/>
    ///      <sys:String x:Key="TitleLabel">[b]LOCALIZATION SAMPLE[/b]</sys:String>
    ///      <sys:String x:Key="SoundLabel">A [i]sound[/i] label</sys:String>
    ///    </ResourceDictionary>
    ///
    /// </summary>
    [ContentProperty("ResourceKey")]
    public class LocExtension : MarkupExtension
    {
        private string _resourceKey;

        public LocExtension()
        {
        }

        public LocExtension(string resourceKey)
        {
            _resourceKey = resourceKey;
        }

        /// <summary>
        /// Gets or sets the key to use when finding a resource in the active localization ResourceDictionary. 
        ///
        /// Usage:
        ///
        ///    <Grid
        ///        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        ///        xmlns:noesis="clr-namespace:NoesisGUIExtensions"
        ///        noesis:Loc.Source="Locale_fr.xaml">
        ///        <TextBlock Text="{noesis:Loc Title}"/>
        ///        <Image Source="{noesis:Loc IntroBackground}"/>
        ///    </Grid>
        ///
        /// </summary>
        public string ResourceKey
        {
            get { return _resourceKey; }
            set { _resourceKey = value; }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget valueTarget = serviceProvider as IProvideValueTarget;
            if (valueTarget == null)
            {
                return null;
            }

            DependencyObject target = (DependencyObject)valueTarget.TargetObject;
            DependencyProperty targetProperty = (DependencyProperty)valueTarget.TargetProperty;

            LocMonitor monitor = (LocMonitor)target.GetValue(MonitorProperty);

            if (monitor == null)
            {
                monitor = new LocMonitor(target);
                target.SetValue(MonitorProperty, monitor);
            }

            return monitor.AddDependencyProperty(targetProperty, _resourceKey);
        }

        #region Source attached property

        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.RegisterAttached(
                "Source",
                typeof(Uri),
                typeof(LocExtension),
                new PropertyMetadata(null, SourceChangedCallback)
            );

        private static void SourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Uri source = (Uri)d.GetValue(SourceProperty);

            if (source == null)
            {
                d.SetValue(ResourcesProperty, null);
                return;
            }

            ResourceDictionary resourceDictionary = new ResourceDictionary
            {
                Source = source
            };

            d.SetValue(ResourcesProperty, resourceDictionary);
        }

        public static Uri GetSource(UIElement target)
        {
            return (Uri)target.GetValue(SourceProperty);
        }

        public static void SetSource(UIElement target, Uri value)
        {
            target.SetValue(SourceProperty, value);
        }

        #endregion

        #region Resources attached property

        private static readonly DependencyProperty ResourcesProperty =
            DependencyProperty.RegisterAttached(
                ".Resources",
                typeof(ResourceDictionary),
                typeof(LocExtension),
                new FrameworkPropertyMetadata(null, flags: FrameworkPropertyMetadataOptions.Inherits, ResourcesChangedCallback)
            );

        private static void ResourcesChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            LocMonitor monitor = (LocMonitor)d.GetValue(MonitorProperty);

            if (monitor != null)
            {
                if (monitor.TargetObject != d)
                {
                    monitor = monitor.Clone(d);
                    d.SetValue(MonitorProperty, monitor);
                }
                monitor.InvalidateResources((ResourceDictionary)e.NewValue);
            }
        }

        public static ResourceDictionary GetResources(DependencyObject dependencyObject)
        {
            return (ResourceDictionary)dependencyObject.GetValue(ResourcesProperty);
        }

        #endregion

        #region Monitor attached property

        private static readonly DependencyProperty MonitorProperty =
            DependencyProperty.RegisterAttached(
                ".Monitor",
                typeof(LocMonitor),
                typeof(LocExtension),
                new PropertyMetadata(null)
            );

        #endregion
    }

    internal class LocMonitor
    {
        private readonly List<Tuple<DependencyProperty, string>> _monitoredDependencyProperties =
            new List<Tuple<DependencyProperty, string>>();

        public DependencyObject TargetObject { get; }

        public LocMonitor(DependencyObject targetObject)
        {
            TargetObject = targetObject;
        }

        public object AddDependencyProperty(DependencyProperty targetProperty, string resourceKey)
        {
            ResourceDictionary resourceDictionary = LocExtension.GetResources(TargetObject);

            for (int i = 0; i < _monitoredDependencyProperties.Count; i++)
            {
                if (_monitoredDependencyProperties[i].Item1 == targetProperty)
                {
                    _monitoredDependencyProperties[i] =
                        new Tuple<DependencyProperty, string>(_monitoredDependencyProperties[i].Item1, resourceKey);

                    return Evaluate(targetProperty, resourceKey, resourceDictionary);
                }
            }

            _monitoredDependencyProperties.Add(new Tuple<DependencyProperty, string>(targetProperty, resourceKey));

            return Evaluate(targetProperty, resourceKey, resourceDictionary);
        }

        public void InvalidateResources(ResourceDictionary resourceDictionary)
        {
            foreach (Tuple<DependencyProperty, string> entry in _monitoredDependencyProperties)
            {
                TargetObject.SetValue(entry.Item1,
                    Evaluate(entry.Item1, entry.Item2, resourceDictionary));
            }
        }

        public LocMonitor Clone(DependencyObject targetObject)
        {
            LocMonitor clone = new LocMonitor(targetObject);
            clone._monitoredDependencyProperties.AddRange(_monitoredDependencyProperties);
            return clone;
        }

        private static object Evaluate(DependencyProperty targetProperty, string resourceKey,
            ResourceDictionary resourceDictionary)
        {
            if (resourceDictionary != null && resourceDictionary.Contains(resourceKey))
            {
                object resource = resourceDictionary[resourceKey];
                if (resource != null)
                {
                    return resource;
                }

                Console.WriteLine($"[NOESIS/E] Resource key '{resourceKey}' not found in Loc Resources");
            }

            if (targetProperty.PropertyType == typeof(string))
            {
                return $"<Loc !{resourceKey}>";
            }

            return null;
        }
    }
}
LocExtension.cs (9,422 bytes)   
samc

samc

2023-10-06 19:55

reporter   ~0008795

Ah thank you, will give it a shot!

Is there any further direction on how to use this exactly? Do I add the LocExtension as a propety of the data trigger or is it on the resource dictionary itself?

thanks,
sam

sfernandez

sfernandez

2023-10-11 10:26

manager   ~0008803

The Loc extension can be used normally in any element of the ControlTemplate or DataTemplate:


<ControlTemplate TargetType="Button">
<StackPanel Background="Silver">
<TextBlock Text="{noesis:Loc ButtonHeader}"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>

Do you plan to use it in any other situations in the template?

samc

samc

2023-10-11 20:17

reporter   ~0008807

Normally, in my user control, I need an additional line like this at the top:

[code]
noesis:Loc.Resources="{Binding SelectedLanguage.Resources}"
[/code]

Is that not necessary for templates? Or does that belong in the resource dictionary somewhere? I just wasn't sure...

sfernandez

sfernandez

2023-10-11 20:36

manager   ~0008808

When the template gets applied in a XAML, it will resolve the Loc extension against the Resources that are available in that XAML.
So you don't have to worry about the Loc.Resource in the dictionary, only in the XAML files where you are going to use the templates.
Will that be enough in your case?

samc

samc

2023-10-11 20:55

reporter   ~0008809

Yes this should do - testing now! thanks!

samc

samc

2023-10-11 22:06

reporter   ~0008810

totally works, you can close this. thanks!

Issue History

Date Modified Username Field Change
2023-10-03 21:17 samc New Issue
2023-10-04 11:58 jsantos Assigned To => sfernandez
2023-10-04 11:58 jsantos Status new => assigned
2023-10-04 11:58 jsantos Target Version => 3.2.3
2023-10-05 10:19 sfernandez Note Added: 0008791
2023-10-05 10:19 sfernandez File Added: LocExtension.cs
2023-10-05 10:19 sfernandez Status assigned => feedback
2023-10-06 19:55 samc Note Added: 0008795
2023-10-06 19:55 samc Status feedback => assigned
2023-10-11 10:26 sfernandez Status assigned => feedback
2023-10-11 10:26 sfernandez Note Added: 0008803
2023-10-11 20:17 samc Note Added: 0008807
2023-10-11 20:17 samc Status feedback => assigned
2023-10-11 20:36 sfernandez Status assigned => feedback
2023-10-11 20:36 sfernandez Note Added: 0008808
2023-10-11 20:55 samc Note Added: 0008809
2023-10-11 20:55 samc Status feedback => assigned
2023-10-11 22:06 samc Note Added: 0008810
2024-01-22 11:47 sfernandez Target Version 3.2.3 => 3.2.4
2024-01-22 11:49 sfernandez Target Version 3.2.4 => 3.2.3
2024-01-22 12:24 sfernandez Assigned To sfernandez => maherne
2024-01-25 11:01 maherne Status assigned => resolved
2024-01-25 11:01 maherne Resolution open => fixed
2024-01-25 11:01 maherne Fixed in Version => 3.2.3
2025-10-10 13:29 jsantos Category Unity3D => Unity