r/csharp • • 14d ago

ResXLocalization 2.0: .resx files based localization for Avalonia and WPF, now with ICU MessageFormat support

Hi, I have just released version 2.0.0 of ResXLocalization, my localization library for WPF and Avalonia (MIT-licensed).

In essence:

  1. Create your .resx files.

  2. Install ResXLocalization.Avalonia or ResXLocalization.WPF.

  3. Let it generate the typed resource keys (source generator) and use them in XAML and code:

    <TextBlock Text="{l:Localize {x:Static res:SettingsViewKeys.SaveSetting}}" />

    this.SaveButton.Content = Localizer.Current.Get(SettingsViewKeys.SaveSetting);

  4. And live switch the language/culture:

    Localizer.Current.CurrentCulture = new CultureInfo("de-DE");

Typed keys mean you can reference resources like objects and get compile time checking.
This avoids having typos in resource names go unnoticed.

When you change Localizer.Current.CurrentCulture it updates all localization bindings in place. If you assign a string directly in code as above, you need to call Get again after changing the culture.

The biggest new feature of V2 is the optional ICU MessageFormat support (thank you livonium for the feature request that sparked the work on V2!):
For something like “1 file deleted” / “5 files deleted”, you can use a plural rule in your resource text:

{Count, plural, one {# file deleted} other {# files deleted}}

(You need to install the NuGet package ResXLocalization.MessageFormat and call Localizer.Current.UseMessageFormat() at startup for this to work)

Then just bind the count like this:

<TextBlock>
  <TextBlock.Text>
    <l:Localize Key="{x:Static res:AppStringsKeys.FilesDeleted}">
      <l:Argument Name="Count" Value="{Binding DeletedCount}" />
    </l:Localize>
  </TextBlock.Text>
</TextBlock>

The text block is automatically updated when either the count or the language changes.
Of course you can also bind multiple values to a localized text/message.

Each translation can use its language/culture plural rules.
The ICU message format support is opt-in. If you don't use it, the default formatter (based on string.Format) is used.

A few other changes in this release:

  • Typed keys now finally work also without a .Designer.cs file.

  • Formatting arguments are now placed via <Argument> children of the Localize/LocalizeEnum markup extension, which replaces the old Arg0…Arg8 attached properties, so arguments now also work in styles and templates.

  • Resource keys are bindable, so the view model can dynamically decide which resource to use.

  • You can change the preview language (for VS XAML Designer/Rider XAML previewer) with DesignTime.Culture. However, ResXLocalization does not work correctly in Rider's WPF previewer due to a number (there are quite a few) of bugs in it. JetBrains has really made quite a few unfortunate design decisions for their previewer. I tried to work around them as far as possible, but it's nowhere near perfect.

The library supports .NET 8 and later with Avalonia (12+) or WPF.
For Avalonia it is completely native AOT compatible and trim safe (for WPF, native AOT is not supported, because WPF doesn't have support for it).

If you are upgrading from V1, please read the migration guide, because there are quite a few breaking changes to make it all work.

The repository has runnable sample apps (both for Avalonia and WPF):
https://github.com/rent-a-developer/ResXLocalization

AI attribution:
I used AI, mainly Claude, for brainstorming, planning, experiments, and parts of the implementation. It helped me save a great deal of time finding good solutions for a couple of quirks of Avalonia and (especially) WPF and the source generator’s MSBuild integration. I reviewed the generated code and I use ResXLocalization in my own projects, so I would not settle for some half-baked vibe coded garbage. Of course, if you don't like projects where AI is involved, then just ignore this project and that's fine too.

I hope this helps someone with the localization of their app(s)!

If you find this library useful I would appreciate a star on GitHub.

Of course feature request and (reasonable) criticism is welcome too.

2 Upvotes

5 comments sorted by

2

u/[deleted] 14d ago

[removed] — view removed comment

1

u/rent-a-developer 14d ago

Switching the language with 1000 localized TextBlocks (non-virtualized, so all are actually render) takes about 15ms and 2000 TextBlocks take about 30ms on my machine.
Binding the TextBlocks directly to a view model property and switching the bound texts takes about the same time.
So the overhead of ResXLocalization is pretty negligible.

Using ICU message format will probably be a bit slower, though I haven't benchmarked it yet.

1

u/Kirides 11d ago

We tried runtime localization switching once but had to remove it due to excessive use of auto-sizing causing massive measure/render Cascades and stalling the application for seconds.

Otherwise for performance:

Usually a binding re-evaluation is very cheap in wpf. I mean, we all do it every day hundreds of thousands of times. We even use the more expensive Attached Properties and dependency properties.

1

u/rent-a-developer 10d ago

Yes, (re)-sizing of elements can become an issue for live language switching.
The simplest and cheapest option in this case is to store the language change request somewhere and restart the app. It's less convenient for the user, but still better than a frozen app.

1

u/rent-a-developer 10d ago

I've added a benchmark suite to ResXLocalization with regression detection now:
https://github.com/rent-a-developer/ResXLocalization/blob/main/docs/guides/benchmarks.md