Helper tool to localize Radzen.Blazor components and applications generated by RBS

We all know how frustrating and time-consuming component localization can be. That's why I took it upon myself to find a solution. Although LocoMat may not be perfect and may have some bugs, it has successfully helped me simplify the localization process in my own projects.

I strongly believe that sharing is caring, and I want to extend the benefits of LocoMat to the developer community. I understand that many non-English developers face the same localization struggles, and LocoMat can be a valuable tool for them.

So, with all its imperfections, I present to you LocoMat—a tool designed to automate and simplify the localization of components. It may have some rough edges, but it has been tested and worked for me. I hope that by sharing it, we can collectively improve and refine LocoMat, making it an invaluable asset for developers worldwide. Let's work together to make component localization a breeze with LocoMat!

LocoMat

LocoMat is a powerful tool designed to simplify the localization of Blazor Razor components in applications built with Radzen Blazor Studio. It provides automation and scaffolding features to streamline the localization process.

Introduction

LocoMat helps you automate the localization of Blazor Razor components by generating resource files from .razor files and updating the corresponding localized resources file. It focuses specifically on providing localization support for Radzen Blazor components.

The tool supports various Radzen Blazor components such as RadzenTemplateForm, RadzenDropDownDataGridColumn, RadzenDataGridColumn, RadzenLabel, RadzenRequiredValidator, RadzenButton, and RadzenPanelMenuItem.

Features

  • Generate resource files from .razor files in your Blazor application
  • Translate resource files to different languages using the Translator API
  • Easy-to-use command-line interface with shortcuts for faster input
  • Scaffold localization for Radzen.Blazor components

Installation

Install LocoMat as a global dotnet tool:

dotnet tool install --global LocoMat

To uninstall LocoMat, use the following command:

dotnet tool uninstall --global LocoMat

For updating LocoMat, run:

dotnet tool update --global LocoMat

Usage

Run LocoMat using the following command:

LocoMat <command> [options]

Commands

  • localize: Localizes the project.
  • scaffold: Scaffolds localization of Radzen.Blazor components.
  • translate: Translates resource files.
  • restore: Restores the original files from backup.

For detailed command options and examples, refer to the LocoMat GitHub repository.

GitHub Repository

Visit the LocoMat GitHub repository for more information, documentation, and to contribute to the project.

Hi @chlupac,

It would be helpful to show the output of this tool as an example e.g. before / after.

Localization Example: CRM RBS Sample Application

Hello,

I want to share an example of localization using LocoMat. I took the CRM sample from Radzen samples and processed it to create a (almost) fully localized application in Czech. You can find the source code on the GitHub repository:

CRM Sample - LocoMat GitHub Repository

This example demonstrates how LocoMat can be used to automate the localization process, making it easier to create applications in different languages. Feel free to explore the code and see how LocoMat simplifies the localization of Blazor applications.

In this example is new RadzenSupport folder, which contains the scaffolded localization support for all Radzen controls. It includes the necessary resource files and code for localizing Radzen controls. This folder is created with scaffold command.

How scaffold works?

  1. Creates overrided control which takes text from resource, for example DataFilter, original text is written to resource file
public class RadzenDataFilterLocalized<TItem> : RadzenDataFilter<TItem>
{
    [Inject] RadzenLocalizer L { get; set; }
    protected override void OnInitialized()
    {
      FilterText = L["RadzenDataFilter.FilterText"] ?? FilterText;
      EnumFilterSelectText = L["RadzenDataFilter.EnumFilterSelectText"] ?? EnumFilterSelectText;
      AndOperatorText = L["RadzenDataFilter.AndOperatorText"] ?? AndOperatorText;
      OrOperatorText = L["RadzenDataFilter.OrOperatorText"] ?? OrOperatorText;
      ApplyFilterText = L["RadzenDataFilter.ApplyFilterText"] ?? ApplyFilterText;
      ClearFilterText = L["RadzenDataFilter.ClearFilterText"] ?? ClearFilterText;
      AddFilterText = L["RadzenDataFilter.AddFilterText"] ?? AddFilterText;
      RemoveFilterText = L["RadzenDataFilter.RemoveFilterText"] ?? RemoveFilterText;
      AddFilterGroupText = L["RadzenDataFilter.AddFilterGroupText"] ?? AddFilterGroupText;
      EqualsText = L["RadzenDataFilter.EqualsText"] ?? EqualsText;
      NotEqualsText = L["RadzenDataFilter.NotEqualsText"] ?? NotEqualsText;
      LessThanText = L["RadzenDataFilter.LessThanText"] ?? LessThanText;
      LessThanOrEqualsText = L["RadzenDataFilter.LessThanOrEqualsText"] ?? LessThanOrEqualsText;
      GreaterThanText = L["RadzenDataFilter.GreaterThanText"] ?? GreaterThanText;
      GreaterThanOrEqualsText = L["RadzenDataFilter.GreaterThanOrEqualsText"] ?? GreaterThanOrEqualsText;
      EndsWithText = L["RadzenDataFilter.EndsWithText"] ?? EndsWithText;
      ContainsText = L["RadzenDataFilter.ContainsText"] ?? ContainsText;
      DoesNotContainText = L["RadzenDataFilter.DoesNotContainText"] ?? DoesNotContainText;
      StartsWithText = L["RadzenDataFilter.StartsWithText"] ?? StartsWithText;
      IsNotNullText = L["RadzenDataFilter.IsNotNullText"] ?? IsNotNullText;
      IsNullText = L["RadzenDataFilter.IsNullText"] ?? IsNullText;
      IsEmptyText = L["RadzenDataFilter.IsEmptyText"] ?? IsEmptyText;
      IsNotEmptyText = L["RadzenDataFilter.IsNotEmptyText"] ?? IsNotEmptyText;
        base.OnInitialized();
    }
}
  1. Registering component overrides
{
    public static IServiceCollection AddRadzenLocalization(this IServiceCollection services)
    {
        var componentActivator = new OverridableComponentActivator();

        componentActivator.RegisterOverride(typeof(PagedDataBoundComponent<>), typeof(PagedDataBoundComponentLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenColorPicker), typeof(RadzenColorPickerLocalized));
        componentActivator.RegisterOverride(typeof(RadzenDataFilter<>), typeof(RadzenDataFilterLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenDataGrid<>), typeof(RadzenDataGridLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenDataList<>), typeof(RadzenDataListLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenDropDown<>), typeof(RadzenDropDownLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenDropDownDataGrid<>), typeof(RadzenDropDownDataGridLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenFileInput<>), typeof(RadzenFileInputLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenGrid<>), typeof(RadzenGridLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenLogin), typeof(RadzenLoginLocalized));
        componentActivator.RegisterOverride(typeof(RadzenPager), typeof(RadzenPagerLocalized));
        componentActivator.RegisterOverride(typeof(RadzenScheduler<>), typeof(RadzenSchedulerLocalized<>));
        componentActivator.RegisterOverride(typeof(RadzenSteps), typeof(RadzenStepsLocalized));
        componentActivator.RegisterOverride(typeof(RadzenUpload), typeof(RadzenUploadLocalized));

        services.AddSingleton<RadzenLocalizer>();
        services.AddSingleton<IComponentActivator>(componentActivator);

        return services;
    }
  1. register activator in Program.cs
builder.Services.AddRadzenLocalization();
var app = builder.Build();

OverridableComponentActivator provides a way to dynamically replace or override component types during runtime, allowing for flexibility and customization in component instantiation. In this case by replacing original component with localized.

Localization

finds localizable strings, replaces them with localizer call and writes key/value pair to resource file.