View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0002710 | NoesisGUI | Unity | public | 2023-10-03 21:17 | 2024-01-25 11:01 |
| Reporter | samc | Assigned To | maherne | ||
| Priority | normal | Severity | major | ||
| Status | resolved | Resolution | fixed | ||
| Product Version | 3.2.1 | ||||
| Target Version | 3.2.3 | Fixed in Version | 3.2.3 | ||
| Summary | 0002710: 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 | ||||
| Platform | Windows | ||||
|
The following patched file should fix the problem and allow using the Loc extension inside templates. 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;
}
}
}
|
|
|
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, |
|
|
The Loc extension can be used normally in any element of the ControlTemplate or DataTemplate:
Do you plan to use it in any other situations in the template? |
|
|
Normally, in my user control, I need an additional line like this at the top: [code] Is that not necessary for templates? Or does that belong in the resource dictionary somewhere? I just wasn't sure... |
|
|
When the template gets applied in a XAML, it will resolve the Loc extension against the Resources that are available in that XAML. |
|
|
Yes this should do - testing now! thanks! |
|
|
totally works, you can close this. thanks! |
|
| 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 |