mrepoDev / MrMeeseeks.ResXToViewModelGenerator

C# Source Generator which turns ResX files into bindable ViewModels

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MrMeeseeks.ResXToViewModelGenerator

C# Source Generator which turns ResX files into bindable ViewModels

Installation

Please use nuget to install it in your project (https://www.nuget.org/packages/MrMeeseeks.ResXToViewModelGenerator/):

dotnet add package MrMeeseeks.ResXToViewModelGenerator

Using it as a project reference or by refencing the library directly won't work out-of-the-box, because you would need to set up your ResX file as "additional files" for the source generator. The nuget package contains a configuration file which'll do that for you.

How it is used

The following descriptions is the recommended usage of the generated code and will focus on WPF project.

Use a single ICurrent[Name]ViewModel instance

Where ever you use localizations, they have to originate from the very same ICurrent[Name]ViewModel instance. Or else they won't switch value whenever your switch the current localization language.

There is a little trick which is useful for WPF applications. Make it a resource in the "App.xaml"-file:

<Application x:Class="MrMeeseeks.ResXLocalizationSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:resXToViewModelGenerator="clr-namespace:MrMeeseeks.ResXToViewModelGenerator"
             Startup="App_OnStartup">
    <Application.Resources>
        <resXToViewModelGenerator:CurrentLocViewModel x:Key="CurrentLocViewModel" />
    </Application.Resources>
</Application>

That way it'll be know for the whole application and you'll be able to reference it from any XAML file:

<StackPanel DockPanel.Dock="Top">
    <TextBlock Text="{Binding CurrentLoc.HelloWorld, Source={StaticResource CurrentLocViewModel}}" />
    <TextBlock Text="{Binding CurrentLoc.GoodNightSun, Source={StaticResource CurrentLocViewModel}}" />
</StackPanel>

Note that the IDEs (both Visual Studio and Rider) will be able to statically check the existence of the properties and will indicate that by syntactic coloring. Which is nice to have. However, be aware, that because the Binding are Reflection-based, the compiler will still build if some of the keys get faulty (for example, if you rename a key and don't adjust its usages accordingly).

Using localizations in code (behind) files

You'll also be able to use localizations in code behind or ViewModel files. There are two approaches which I can think of. But both approaches require you get hold of the ICurrent[Name]ViewModel instance in code rather than the XAML files. I highly recommend to use dependency injection by constructor injection. Make sure that always the resource instance of the App is injected (for example, setup a custom factory method which references the App-resource and set its lifetime to Singleton/SingleInstance). Once you've done that you'll be able to comfortably inject it where ever you want:

public MainWindow(ICurrentLocViewModel currentLocViewModel)
{
    _currentLocViewModel = currentLocViewModel;
}

So, the first approach has less overhead just use the instance and reference the localization which you need:

private void InCodeButton_OnClick(object sender, RoutedEventArgs e)
{
    MessageBox.Show(
        this, 
        _currentLocViewModel.CurrentLoc.InCodeOnDemandText,
        _currentLocViewModel.CurrentLoc.InCodeOnDemandTitle);
}

Unfortunately, this'll only work properly under certain conditions: the localization references (here: InCodeOnDemandText and InCodeOnDemandTitle) are only used as long as the localization language isn't switched. In the above example it is guaranteed, because the MessageBox is modal and has to be closed before a localization switch can happen. Next time the MessageBox is opened the localization references are evaluated anew.

Such constraints are not always given, hence there is another way which has a bit more code involved:

private readonly ICurrentLocViewModel _currentLocViewModel;
private string _inCodeReactiveText;

public string InCodeReactiveText
{
    get => _inCodeReactiveText;
    set
    {
        _inCodeReactiveText = value;
        OnPropertyChanged();
    }
}

public MainWindowViewModel(ICurrentLocViewModel currentLocViewModel)
{
    _currentLocViewModel = currentLocViewModel;
    InCodeReactiveText = _currentLocViewModel.CurrentLoc.InCodeReactiveText;
    currentLocViewModel.PropertyChanged += (_, _) =>
        InCodeReactiveText = _currentLocViewModel.CurrentLoc.InCodeReactiveText;
}

Here, the property InCodeReactiveText represents a localization reference. If the localization language is switch we need to update it and emit a notification. In order to do that we register a callback on the PropertyChanged-event of ICurrentLocViewModel. That way we'll reactively adjust the property anytime the localization language got changed. Be aware that this sample isn't clean on the aspect of properly deregistering from the event!

Sample repository

For demonstration of the localization workflow with this project and another one of mine (https://github.com/Yeah69/MrMeeseeks.ResXTranslationCombinator), I've created a sample repository. Feel free to have a look: https://github.com/Yeah69/MrMeeseeks.ResXLocalizationSample

For more details please have a look into the wiki.

References

  • https://github.com/VocaDB/ResXFileCodeGenerator
    • Basically I used this as a template for this project
    • We share parts like "how to get ResX files to the generator" (AdditionalFiles)
    • But I've decided not to fork, because the purposes differ. I think that both projects work well besides each other and have their own use cases.
    • Still I would have much more difficulties, if I wouldn't have discovered this project. Thanks a lot!

About

C# Source Generator which turns ResX files into bindable ViewModels

License:MIT License


Languages

Language:C# 100.0%