View Issue Details

IDProjectCategoryView StatusLast Update
0004186NoesisGUIStudiopublic2026-01-27 17:34
ReporterKamo Assigned Tomaherne  
PrioritynormalSeverityminor 
Status resolvedResolutionfixed 
Product VersionStudio_Beta 
Target VersionStudio_BetaFixed in VersionStudio_Beta 
Summary0004186: Localization with merged dictionaries doesn't work
Description

In case you have merged dictionaries set, the ResourceKey dropwdown won't show any localization keys to select from even though language_jp.xaml has some. The workaround is to select language_jp.xaml directly for Localization source but that will not have all the values I want

<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">

  <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="language_jp.xaml"/>
  </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>
PlatformAny

Activities

sfernandez

sfernandez

2026-01-13 11:20

manager   ~0011689

As mentioned by the client, it would be good to sort the keys in the dropdown.

maherne

maherne

2026-01-27 13:36

developer   ~0011779

The attached patch updates the localization key property with sorted keys, and support for merged dictionaries.

LocExtensionKey.patch (5,809 bytes)   
Index: LocalizationResourceKeyProperty.cpp
===================================================================
--- LocalizationResourceKeyProperty.cpp	(revision 16539)
+++ LocalizationResourceKeyProperty.cpp	(working copy)
@@ -22,6 +22,69 @@
 
 
////////////////////////////////////////////////////////////////////////////////////////////////////
+static void AddResourceKey(const Type* search, HashSet<Symbol>* keys,
+    const char* key, BaseComponent* value)
+{
+    if (value == nullptr)
+    {
+        return;
+    }
+
+    Symbol keySym(key, Symbol::NullIfNotFound());
+    if (keySym.IsNull() || (!keySym.IsNull() && Reflection::GetType(keySym) != nullptr))
+    {
+        return;
+    }
+
+    // Only check if type matches if we're not searching for generic BaseComponent
+    if (search != TypeOf<BaseComponent>())
+    {
+        // Find the actual type of the resource
+        const Type* resType = value->GetClassType();
+        if (Helper::IsType<BoxedValue>(value))
+        {
+            resType = ((BoxedValue*)value)->GetValueType();
+        }
+        else
+        {
+            resType = Helper::ExtractComponentType(resType);
+        }
+        if (resType == nullptr)
+        {
+            resType = value->GetClassType();
+        }
+
+        if (resType && !search->IsAssignableFrom(resType))
+        {
+            return;
+        }
+    }
+
+    keys->Insert(keySym);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+static void ProcessResources(const ResourceDictionary* resources, const Type* searchType,
+    HashSet<Symbol>* keys)
+{
+    if (resources == nullptr)
+    {
+        return;
+    }
+
+    resources->EnumKeyValues([&searchType, &keys](const char* key, BaseComponent* value)
+    {
+        AddResourceKey(searchType, keys, key, value);
+    });
+
+    ResourceDictionaryCollection* collection = resources->GetMergedDictionaries();
+    for (int i = 0; i < collection->Count(); i++)
+    {
+        ProcessResources(collection->Get(i), searchType, keys);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
 LocalizationResourceKeyProperty::LocalizationResourceKeyProperty(BasePropertySource* src):
     Property(src), mRefreshing(false)
 {
@@ -115,56 +178,24 @@
         }
     }
 
-    // Gather all keys from the dictionary
+    // Gather all keys from this dictionary, and all merged dictionaries
     if (resources && searchType)
     {
-        // TODO: search merged dictionaries?
-        struct Params
+        HashSet<Symbol> keys;
+        ProcessResources(resources, searchType, &keys);
+        for (Symbol key : keys)
         {
-            const Type* search;
-            Collection<Boxed<String>>* keys;
-        } params = { searchType, &mKeys };
-
-        // Go through resources
-        resources->EnumKeyValues([&params](const char* key, BaseComponent* value)
-        {
-            if (value == nullptr)
+            int32_t insertIndex = 0;
+            for (; insertIndex < mKeys.Count(); insertIndex++)
             {
-                return;
-            }
-
-            // Only check if type matches if we're not searching for generic BaseComponent
-            if (params.search != TypeOf<BaseComponent>())
-            {
-                // Find the actual type of the resource
-                const Type* resType = value->GetClassType();
-                if (Helper::IsType<BoxedValue>(value))
+                if (StrCaseCompare(Boxing::Unbox<String>(mKeys.Get(insertIndex)).Str(), key.Str())
+                    > 0)
                 {
-                    resType = ((BoxedValue*)value)->GetValueType();
+                    break;
                 }
-                else
-                {
-                    resType = Helper::ExtractComponentType(resType);
-                }
-                if (resType == nullptr)
-                {
-                    resType = value->GetClassType();
-                }
-
-                if (resType && !params.search->IsAssignableFrom(resType))
-                {
-                    return;
-                }
             }
-
-            Symbol keySym(key, Symbol::NullIfNotFound());
-            if (keySym != Symbol::Null() && Reflection::GetType(keySym) != nullptr)
-            {
-                return;
-            }
-
-            params.keys->Add((Boxed<String>*)Boxing::Box<String>(key).GetPtr());
-        });
+            mKeys.Insert(insertIndex, (Boxed<String>*)Boxing::Box<String>(key.Str()).GetPtr());
+        }
     }
 }
 
@@ -193,7 +224,7 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-const Collection<Boxed<String>>* LocalizationResourceKeyProperty::GetKeys() const
+const ObservableCollection<Boxed<String>>* LocalizationResourceKeyProperty::GetKeys() const
 {
     return &mKeys;
 }
Index: LocalizationResourceKeyProperty.h
===================================================================
--- LocalizationResourceKeyProperty.h	(revision 16539)
+++ LocalizationResourceKeyProperty.h	(working copy)
@@ -32,10 +32,10 @@
     void Refresh() override;
 
     // Get list of keys
-    const Noesis::Collection<Noesis::Boxed<Noesis::String>>* GetKeys() const;
+    const Noesis::ObservableCollection<Noesis::Boxed<Noesis::String>>* GetKeys() const;
 
 private:
-    Noesis::Collection<Noesis::Boxed<Noesis::String>> mKeys;
+    Noesis::ObservableCollection<Noesis::Boxed<Noesis::String>> mKeys;
     bool mRefreshing;
 
     NS_DECLARE_REFLECTION(LocalizationResourceKeyProperty, Property)
LocExtensionKey.patch (5,809 bytes)   

Issue History

Date Modified Username Field Change
2025-05-23 10:34 Kamo New Issue
2025-05-27 19:36 jsantos Assigned To => sfernandez
2025-05-27 19:36 jsantos Status new => assigned
2025-05-27 19:36 jsantos Product Version => Studio_Beta
2025-05-27 19:36 jsantos Target Version => Studio_Beta
2025-05-27 19:37 jsantos Product Version Studio_Beta => 3.2.6
2025-05-27 19:37 jsantos Target Version Studio_Beta => 3.2.8
2025-05-27 19:38 jsantos Product Version 3.2.6 => Studio_Beta
2025-05-27 19:38 jsantos Target Version 3.2.8 => Studio_Beta
2026-01-13 11:20 sfernandez Assigned To sfernandez => maherne
2026-01-13 11:20 sfernandez Description Updated
2026-01-13 11:20 sfernandez Note Added: 0011689
2026-01-27 13:36 maherne Note Added: 0011779
2026-01-27 13:36 maherne File Added: LocExtensionKey.patch
2026-01-27 17:34 sfernandez Status assigned => resolved
2026-01-27 17:34 sfernandez Resolution open => fixed
2026-01-27 17:34 sfernandez Fixed in Version => Studio_Beta