PatTech.Localization.WPF
1.3.0
dotnet add package PatTech.Localization.WPF --version 1.3.0
NuGet\Install-Package PatTech.Localization.WPF -Version 1.3.0
<PackageReference Include="PatTech.Localization.WPF" Version="1.3.0" />
<PackageVersion Include="PatTech.Localization.WPF" Version="1.3.0" />
<PackageReference Include="PatTech.Localization.WPF" />
paket add PatTech.Localization.WPF --version 1.3.0
#r "nuget: PatTech.Localization.WPF, 1.3.0"
#:package PatTech.Localization.WPF@1.3.0
#addin nuget:?package=PatTech.Localization.WPF&version=1.3.0
#tool nuget:?package=PatTech.Localization.WPF&version=1.3.0
Words for WPF
Use the Words extension to put Words in the XAML.
Include Words
Load your Words once, before any window shows up:
public partial class App : Application {
public App() {
WordsBuilder.Create()
// LoadResource reads straight out of your pack resources.
.LoadResource("pack://application:,,,/My-Project;Component/Assets/words.ini")
// Select the language; Digest installs it as Words.Known. The flag also
// points WPF's binding culture at it — see "Match the binding culture".
.Digest("en", includeFrameworkElements: true);
}
}
Use Words in XAML
One namespace gives you everything (pattech.words, the older name, still works):
<Window xmlns:l="https://github.com/pzahra/words"
Title="{l:Words main.title}">
<TextBlock>
<l:WordsInline Key="main.sample-markdown"/>
</TextBlock>
</Window>
{l:Words key}— a markup extension that resolves to the localized string.<l:WordsInline Key="key"/>— an inline that renders the value, markdown and all, inside aTextBlock.
WordsInline also fills format placeholders from its Params property: bind
an array for positional {0} tags, or any other object for {Name} tags read
off its public fields and properties. The inlines re-render whenever Key or
Params changes.
Changing language means restarting
{l:Words} resolves once, when the XAML loads, and WordsInline re-renders
only when its Key or Params change. Neither watches Words.Known, and
that is deliberate, not a gap to fill: a live swap would also have to catch
every LazyWords, every string a view model composed and kept, every title
already set, and it would only hold up in an app that is strict MVVM all the
way down. Do not hot-swap the dictionary in a running UI. Save the choice and
relaunch the process, with --lang=xx on the command line as the samples do
or from a settings file as Wordsmith does, and let the new process load in the
new language.
Match the binding culture to the language
Words' own WordsInline and Words.Format format numbers and dates with the
thread's CurrentCulture, and Digest sets that for you. Bindings are another
matter — a StringFormat, WordsConverter, someone else's converter: WPF hands
them the target element's Language, which defaults to en-US no matter what
language you picked. The WPF Digest overload takes one extra flag to repoint it:
wb.Digest(lang, out var languages, includeFrameworkElements: true);
It is the core Digest — build, install as Words.Known, sync the thread
cultures — plus the process-global Language step you would otherwise spell
out as OverrideMetadata calls: one for controls and one for the TextElement
flow content a bound Run lives in, since a default is not inherited down the
tree. It is one-shot: the first call sets it, later calls leave it. It points at
whichever formatting culture Digest installed, so .UseSystemNumbers() before
it gives you English words with the system's number and date formats in the
bindings too. A single element or binding can still say otherwise with
xml:lang or ConverterCulture, as in any WPF app. Leave the flag off (or call
the core Digest) to keep WPF's own default.
Make hyperlinks go somewhere
WPF hyperlinks raise RequestNavigate and then do nothing. Register the
application-wide handler once at startup and every link the markdown renders
routes through it — custom schemes make in-app commands:
Hyperlink.RegisterGlobalNavigateHandler(uri => {
if (uri.Scheme is "appcmd") {
// Handle application command hyperlinks.
}
else if (uri.Scheme is "http" or "https" or "mailto") {
// Only hand the shell schemes you trust to open externally: a rendered
// value is display text, so never shell-open an arbitrary scheme (file:
// and friends would run local things). An unlisted scheme is ignored.
Process.Start(new ProcessStartInfo(uri.ToString()) { UseShellExecute = true });
}
});
There is one global handler: registering again replaces it, and disposing the returned subscription unregisters it.
Put pictures in your Words
Markdown images work in any rendered value, with the URI scheme deciding where the picture comes from:
[main.save-hint]
value=Press  to save.
Out of the box the parser speaks staticres: and dynres: (a resource by
x:Key, found from where the image lands in the tree — the window or user
control it is in, then the application — the way {StaticResource} and
{DynamicResource} are; dynres: stays live, so swapping the resource for a
theme change re-renders it), pack: (WPF pack URIs), resx: (a Resources
class in your loaded assemblies), and assets: (files under the application's
Assets folder. It's a convenience, not a security boundary: the path is
lexically clamped to that folder — ../ and rooted paths resolve to nothing —
and the scheme only ever loads images, so a symlink someone planted inside
Assets is out of scope). A resource renders as a fresh visual every time: an
ImageSource in an Image, a Geometry in a filled Path, a DataTemplate
as newly loaded content — which is how you reuse an element, since one instance
can't live under two parents. Any other resource type throws, as it would
anywhere else in WPF.
Query options width, height, background, and foreground
apply whatever the scheme; the query carries display options, not asset
identity, so resolvers always receive the URI with it already split off.
background and foreground take a color, or a brush resource spelled the way
the image schemes are — staticres:key or dynres:key, found from where the
image lands, and dynres: follows a theme swap (a Color resource is wrapped in
a brush; any other type throws; a missing key leaves the default standing — black
fill, no border — and gripes, rather than going transparent).
Raster images render at their natural size unless width or height says
otherwise; geometry, having no natural size, defaults to the font height.
Anything that fails to resolve degrades to its alt text as [🖼️!alt], because
a missing icon should never eat your sentence.
Teach it new schemes by registering an IImageSchemeResolver on the shared
parser at startup — say, Material Design icons:
class PackIconResolver : IImageSchemeResolver {
public FrameworkElement? Resolve(Uri source, ImageOptions options)
=> Enum.TryParse<PackIconKind>(source.AbsolutePath.TrimStart('/'), out var kind)
? new PackIcon { Kind = kind, Foreground = options.Foreground ?? Brushes.Black }
: null;
}
// at startup:
MarkdownParser.Default.ImageSchemes["md"] = new PackIconResolver();
// and now `` gives you Words with icons in them.
Convert Words
For values that only exist at runtime, there are converters:
WordsConverter— formats a bound value into the Words template named byConverterParameter.MarkdownConverter— turns a markdown string into WPF inlines.EnumDescriptionConverter— turns a[Words]-decorated enum value into its display text; the ConverterParameter picks theDescribeformat (tooltip, description, unit…).FlagsDescriptionConverter— the same for[Flags]combinations, as a list of descriptions or one delimited string (AsArray="False").ArrayMultiConverter— gathers aMultiBindinginto the array thatWordsInline.Paramswants.ResourceVisualConverter— turns a resource value (ImageSource,Geometry,DataTemplate) into a fresh visual; what thestaticres:/dynres:image schemes render through, should you want the same from a binding.
None of them need configuring, so the package ships them pre-instantiated in
Converters.xaml — merge it once:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/PatTech.Localization.WPF;component/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
and every view can say {StaticResource WordsMarkdown}, WordsFormat,
WordsEnumDescription, WordsFlagsDescription (joined text),
WordsFlagsDescriptionList (one description per flag), WordsParamsArray, or
WordsResourceVisual.
The rest of the suite
- PatTech.Localization.Core — the engine:
words.inifiles, lookups by key, languages and fallbacks,{0}/{Name}parameters,{>key}references, a markdown dialect. - PatTech.Localization.Avalonia — the same as this, for Avalonia's AXAML.
- PatTech.Localization.Analyzer — the
[Localized]attribute and rule PTL001, which flags a localized seam handed a raw string. It arrives with Core. - Wordsmith — the desktop editor for
words.inifiles, published on GitHub Releases.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 is compatible. net9.0-windows was computed. net10.0-windows was computed. net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- PatTech.Localization.Core (>= 1.3.0)
-
net8.0-windows7.0
- PatTech.Localization.Core (>= 1.3.0)
- System.Drawing.Common (>= 10.0.10)
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.3.0 | 60 | 9/19/2026 |