diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/en-US/Resources.resw
new file mode 100644
index 00000000000..7c909c61815
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/en-US/Resources.resw
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Hello!
+
+
+ Resource String Markup Extension
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/es-ES/Resources.resw b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/es-ES/Resources.resw
new file mode 100644
index 00000000000..531bd774c7e
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Strings/es-ES/Resources.resw
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Hola!
+
+
+ Resource String Markup Extension
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
index 0a9bf08f523..bab9877f54b 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
+++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
@@ -276,6 +276,7 @@
+
@@ -389,6 +390,8 @@
PreserveNewest
+
+
@@ -512,6 +515,9 @@
MetadataControlPage.xaml
+
+ ResourceStringPage.xaml
+
AttachedDropShadowPage.xaml
@@ -644,6 +650,7 @@
+
@@ -999,6 +1006,10 @@
Designer
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceString.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceString.png
new file mode 100644
index 00000000000..ceb19f77980
Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceString.png differ
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml
new file mode 100644
index 00000000000..e4dd6f8eebd
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml.cs
new file mode 100644
index 00000000000..d31dc3b797c
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringPage.xaml.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
+{
+ ///
+ /// A page that shows how to use the offset behavior.
+ ///
+ public sealed partial class ResourceStringPage : IXamlRenderListener
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ResourceStringPage()
+ {
+ InitializeComponent();
+ }
+
+ public void OnXamlRendered(FrameworkElement control)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringXaml.bind
new file mode 100644
index 00000000000..100516a97fa
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ResourceString/ResourceStringXaml.bind
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+ Get a greating from the English resource context:
+
+
+
+ Or get a greeting from the Spanish resource context:
+
+
+
+
+ You can alternatively get a greeting using x:Bind for optimized compiled bind:
+
+
+ {x:Bind ui:ResourceStringExtension.GetValue('HelloText')}
+
+
+ {x:Bind ui:ResourceStringExtension.GetValue('HelloText', 'es-ES')}
+
+
+
+
+ See our blog post on #ifdef Windows:
+ Use a Custom Resource Markup Extension to Succeed at UI String Globalization!
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml
index a3a001645f7..78d31dc5f06 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml
@@ -39,6 +39,8 @@
+
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
index 8cdfcc3a619..ef72da0f55f 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
@@ -1266,11 +1266,20 @@
"Name": "OnDevice",
"Type": "OnDevicePage",
"About": "The OnDevice markup extension allows you to customize UI appearance on a per-DeviceFamily basis.",
- "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDeviceExtension.cs",
+ "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDeviceExtension.cs",
"Icon": "/SamplePages/OnDevice/OnDevice.png",
"XamlCodeFile": "OnDeviceXaml.bind",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/extensions/OnDeviceMarkup.md"
},
+ {
+ "Name": "ResourceString",
+ "Type": "ResourceStringPage",
+ "About": "The ResourceString markup extension allows you to easily access resource strings through bindings instead of Uid.",
+ "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/ResourceStringExtension.cs",
+ "Icon": "/SamplePages/ResourceString/ResourceString.png",
+ "XamlCodeFile": "ResourceStringXaml.bind",
+ "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/extensions/ResourceStringMarkup.md"
+ },
{
"Name": "IconExtensions",
"Type": "IconExtensionsPage",
@@ -1284,7 +1293,7 @@
"Name": "EnumValuesExtension",
"Type": "EnumValuesExtensionPage",
"About": "The EnumValuesExtension markup extension allows you to easily retrieve a collection of all values from an enum type.",
- "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/EnumValuesExtension.cs",
+ "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/EnumValuesExtension.cs",
"Icon": "/Assets/Helpers.png",
"XamlCodeFile": "EnumValuesExtensionXaml.bind",
"CodeFile": "EnumValuesExtensionCode.bind",
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/ResourceStringExtension.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/ResourceStringExtension.cs
new file mode 100644
index 00000000000..be877b32952
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/ResourceStringExtension.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.ApplicationModel.Resources;
+using Windows.ApplicationModel.Resources.Core;
+using Windows.UI.Xaml.Markup;
+
+namespace Microsoft.Toolkit.Uwp.UI
+{
+ ///
+ /// Xaml extension to return a value from resource file associated with a resource key
+ ///
+ [MarkupExtensionReturnType(ReturnType = typeof(string))]
+ public sealed class ResourceStringExtension : MarkupExtension
+ {
+ private static readonly ResourceLoader resourceLoader = ResourceLoader.GetForViewIndependentUse();
+
+ ///
+ /// Gets or sets associated ID from resource strings.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the associated language resource to use (ie: "es-ES").
+ /// Defaults to the OS language of current view.
+ ///
+ public string Language { get; set; }
+
+ ///
+ protected override object ProvideValue()
+ {
+ return GetValue(this.Name, this.Language);
+ }
+
+ ///
+ /// Gets a string value from resource file associated with a resource key.
+ ///
+ /// Resource key name.
+ /// A string value from resource file associated with a resource key.
+ public static string GetValue(string name)
+ {
+ // This function is needed to accomodate compiled function usage without second paramater,
+ // which doesn't work with optional values.
+ return resourceLoader.GetString(name);
+ }
+
+ ///
+ /// Gets a string value from resource file associated with a resource key.
+ ///
+ /// Resource key name.
+ /// Optional language of the associated resource to use (ie: "es-ES").
+ /// default is the OS language of current view.
+ /// A string value from resource file associated with a resource key.
+ public static string GetValue(string name, string language = "")
+ {
+ if (string.IsNullOrEmpty(language))
+ {
+ return GetValue(name);
+ }
+
+ // Not using ResourceContext.GetForCurrentView nor GetForViewIndependentUse
+ ResourceContext resourceContext = new() { Languages = new[] { language } };
+ var resourceMap = ResourceManager.Current.MainResourceMap.GetSubtree("Resources");
+
+ return resourceMap.GetValue(name, resourceContext).ValueAsString;
+ }
+
+ }
+}
diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln
index c09b6446a9a..21f6aa5ef03 100644
--- a/Windows Community Toolkit.sln
+++ b/Windows Community Toolkit.sln
@@ -205,6 +205,8 @@ Global
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|Any CPU.Build.0 = Debug|x86
+ {719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|Any CPU.Deploy.0 = Debug|x86
{719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|ARM.ActiveCfg = Debug|ARM
{719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|ARM.Build.0 = Debug|ARM
{719C43C6-8753-4395-ADAA-2FCC70F76BF3}.Debug|ARM.Deploy.0 = Debug|ARM
@@ -387,6 +389,7 @@ Global
{BAB1CAF4-C400-4A7F-A987-C576DE63CFFD}.Release|x86.Build.0 = Release|x86
{BAB1CAF4-C400-4A7F-A987-C576DE63CFFD}.Release|x86.Deploy.0 = Release|x86
{1AE2CB5C-58A0-4F12-8E6F-2CD4AAADB34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1AE2CB5C-58A0-4F12-8E6F-2CD4AAADB34C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AE2CB5C-58A0-4F12-8E6F-2CD4AAADB34C}.Debug|ARM.ActiveCfg = Debug|ARM
{1AE2CB5C-58A0-4F12-8E6F-2CD4AAADB34C}.Debug|ARM.Build.0 = Debug|ARM
{1AE2CB5C-58A0-4F12-8E6F-2CD4AAADB34C}.Debug|ARM64.ActiveCfg = Debug|ARM64