December 15th, 2020

Localize .NET applications with machine-translation

David Pine
Senior Content Developer

In this post, I’m going to introduce you to a GitHub Action that creates machine-translations for .NET localization. GitHub Actions allow you to build, test, and deploy your code right from GitHub, but they also allow for other workflows. You can perform nearly any action imaginable against your source code as it evolves. With the Machine Translator GitHub Action, you configure a workflow to automatically create pull requests as translation source file change.

You can use localization with Blazor WebAssembly (Wasm) to change the displayed language of a rendered website. Localization support in .NET is nothing new. It’s possible with translation files, for example, *.{locale}.resx, *.{locale}.xliff, or *.{locale}.restext to name a few. The CultureInfo class is used along with these translation files and various other .NET employed mechanics. However, maintaining translation files can be tedious and time-consuming. With GitHub Actions and Azure Cognitive Services Translator, you can set up a workflow to automatically create pull requests that provide machine-translated files.

Azure Cognitive Services Translator

Cognitive Services Translator is a cloud-based machine translation service from Azure. It powers the GitHub Action, providing the root translation functionality. To use the action, you will need a Cognitive Services Translator resource. You can use an existing one, or create a new one. If you do not have an Azure account, you can create one for free. This resource is used to perform the translations from the GitHub Action through the Translator API v3. In other words, as you push code changes to your GitHub repository that include *.en.resx files, this action runs when correctly specified in the workflow.

For more information on filtering when actions run due to changes in specific files, see Workflow syntax for GitHub Actions.

Machine Translator GitHub Action

The Machine Translator GitHub Action is available on the GitHub action marketplace. This GitHub Action does the work of marrying the functionality of the Cognitive Services Translator with your source files. To use this action, you’ll need to create a GitHub workflow. There are a few required inputs (and some optional), most of which are from your Azure Translator resource:

Type Input name Example / Description
Required sourceLocale 'en' The source locale to translate from.
Required subscriptionKey 'c571d5d8xxxxxxxxxxxxxxxxxx56bac3' Cognitive Services Translator subscription key, ideally stored as secret.
Required endpoint 'https://api.cognitive.microsofttranslator.com/' Cognitive Services Translator endpoint, ideally stored as secret.
Optional region 'canadacentral' Cognitive Services Translator region, ideally stored as secret. Optional when using a global translator resource.
Optional toLocales '"es,de,fr"' or '["es","de","fr"]' Limit the scope of the translation targets. If not provided, uses all possible translation targets.

Additionally, the action requires a GITHUB_TOKEN as an environment variable. GitHub automatically creates a GITHUB_TOKEN secret to use in your workflow as an encrypted secret. To define this environment variable, use the following YAML within your workflow (more on this later):

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

For more information, see GitHub Actions: Authentication in a workflow .

Machine Translator design

The Machine Translator action is entirely open source. It is written in TypeScript, and designed to accomplish several key objectives:

  • Determine which languages are available for translation from the translator API
  • Read all translation files (*.resx for example) as source inputs for translation
  • Translate all inputs into the available languages, or configured targets
  • Create (or update existing) translation files

To see how the action is actually implemented, feel free to view the source on the GitHub repository.

The scope of this action is limited to reading input translation files, translating new ones from these sources, and then writing the translation files back to the workspace. Part of the intended workflow composition is to pair this action with two others. The first is the actions/checkout@v2 action, which checks out your repository under the workspace so that the action can access it. The second is the peter-evans/create-pull-request@v3.4.1 action, which will create a pull request if files are changed. Special shout-out to Peter Evans 🎉 for his work here!

Open and active development

This action is actively being developed. It now has full support for the RESX, RESTEXT, and INI file formats, and basic support of XLIFF and PO file formats. XLIFF is another common industry-standard for resource management, while RESTEXT is a simpler INI-based key-value-pair alternative. The Machine Translator automatically handles batching of rate-limited API calls to Cognitive Services Translator. Many thanks to Tim Heuer 🤘🏼 for his collaboration and feedback! To propose ideas, feature requests, or post issues – please do so on the GitHub repository.

“Blazing Translations” demo app

This GitHub Action is based on the notion of resource files and localization in .NET. Any .NET application that follows this localization paradigm is free to use this action. In this way, the action is not limited to just ASP.NET Core Blazor Wasm apps – that is just the demo app of choice for this post. The GitHub repository for the demo app is available at IEvangelist/IEvangelist.BlazingTranslations. The repository has several Secrets, which store encrypted values that can be accessed from a workflow. For more information, see GitHub Action reference: Encrypted Secrets.

The demo app was inspired by Pranav Krishnamoorthy’s LocSample but adds a bit more exemplary source code. Some of the key components are:

  • In the Client project, call the IServiceCollection.AddLocalization() extension method to register localization services
  • Using dependency injection, inject IStringLocalizer<T> where string literals are used
  • Add resource files that shadow Razor components and pages. For example, Index.razor should be accompanied by an Index.en.resx file
  • Replace string literals with resource key-value pairs

Example Razor component

@page "/"

<h1>@HelloWorld</h1>

@Greeting

<SurveyPrompt Title="@SurveyTitle" />

In the preceding Razor markup, there are a few @ directives that call into the code-behind, which simply access their property values. Consider the following Index.razor.cs file, which shadows the Razor component.

using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;

namespace IEvangelist.BlazingTranslations.Client.Pages
{
    public partial class Index
    {
        [Inject]
        public IStringLocalizer<Index> Localizer { get; set; }

        public string SurveyTitle => Localizer[nameof(SurveyTitle)];
        public string Greeting => Localizer[nameof(Greeting)];
        public string HelloWorld => Localizer[nameof(HelloWorld)];
    }
}

The partial class is shared with the Index Razor component’s generated class. As such, this is thought of as the “code-behind”. It uses the [Inject] attribute to inject the IStringLocalizer<Index>. There are three readonly properties which are expressed as localizer indexer accessors. In this example, the corresponding Index.en.resx resource file is similar to the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
  <data name="Greeting" xml:space="preserve">
    <value>Welcome to your new app.</value>
  </data>
  <data name="HelloWorld" xml:space="preserve">
    <value>Hello, world!</value>
  </data>
  <data name="SurveyTitle" xml:space="preserve">
    <value>How is Blazor working for you?</value>
  </data>
</root>

Now that you have your resource file, and Razor components defined, you need to create the workflow.

Create workflow

Workflows are defined within the .github/workflows directory from the root of the repository and are written as YAML files. Consider the following:

name: Create translation pull request
on:
  push:
    branches: [ main ]
    paths:
    - '**.en.resx' # only take action when *.en.resx files change

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Available by default, contextual to the action

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Checks-out repository under the workspace, so that the action can access it
      - uses: actions/checkout@v2

      # Use the machine-translator to automatically translate resource files
      - name: Machine Translator
        id: translator
        uses: IEvangelist/resource-translator@v2.1.1
        with:
          subscriptionKey: ${{ secrets.AZURE_TRANSLATOR_SUBSCRIPTION_KEY }}
          endpoint: ${{ secrets.AZURE_TRANSLATOR_ENDPOINT }}
          region: ${{ secrets.AZURE_TRANSLATOR_REGION }}
          sourceLocale: 'en'

      # Creates a pull request of all translated resource files
      - name: Create pull request
        uses: peter-evans/create-pull-request@v3.4.1
        if: ${{ steps.resource-translator.outputs.has-new-translations }} == 'true'
        with:
          title: '${{ steps.resource-translator.outputs.summary-title }}'
          body: '${{ steps.resource-translator.outputs.summary-details }}'

The preceding workflow definition will run when any *.en.resx file is either created or changed.

Putting it all together

With all the moving pieces in place, you the developer, are empowered to develop as you normally would. As you create and update Razor components and corresponding resource files, pull requests are automatically created for your review with translated resource files. Since pull requests are created, they can be updated by translation specialists if need be – but this will serve as a great starting point nonetheless.

Example pull request

Here is a link to an example automated pull request. The automated pull request was triggered by a commit that simply updated several *.en.resx files. The pull request details a summary of translations.

GitHub pull request #20 from IEvangelist/IEvangelist.BlazingTranslations

Summary

The Azure Cognitive Services Translator API serves as the backbone to the Machine Translator GitHub Action. With great power, comes great responsibility. As a developer, you must wear your ethics-hat at all times, especially when working with artificial intelligence (AI). It might seem as though this GitHub Action can easily elevate your .NET apps to be inclusive of nearly 80 languages, it is irresponsible to make such claims. These are machine translations and should be treated as such. Without direct intervention from a human review, the machine translations may not be conversationally accurate or culturally specific to what you are trying to convey in the text being translated.

While I’m hopeful that you will use this action, I encourage you to also consider limiting the scope of translations to known locales with the toLocales input. Work closely with the stakeholders during the app development, and incrementally solicit feedback on translations from those who are consuming that app.

See also

Author

David Pine
Senior Content Developer

David Pine works in Developer Relations at Microsoft, focusing on .NET and Azure developer content. He is recognized as a Google Developer Expert in Web Technologies, and is Twilio Champion. David focuses on the developer community, actively seeking opportunities to share knowledge through speaking engagements around the world. David advocates for open-source, the .NET Foundation, C#, TypeScript, SignalR, Reactive Extensions, Azure and .NET. He's a founding member and co-host of The .NET Docs ...

More about author

9 comments

Discussion is closed. Login to edit/delete existing comments.

  • Multi apk Club · Edited

    Thanks Man for providing this amazing info. Thanks Alot

  • Lastri Lastri

    Another very important use for machine translation is to create a pseudo translation of your application. A pseudo translation is a translation to a pseudo language that looks very similar to the original language but that can also be identified as not the original language. The benefit of a pseudo translation is that it allows developers to test that the application is localizable while still being able to read the prompts in the application so...

    Read more
  • Mystery Man

    <Sarcasm>Oh, great!</Sarcasm> Even more reason never to touch a non-English version of an app. <Sarcasm>That’s exactly what I needed. Thank you.</Sarcasm>

    • Louis Lecailliez

      I totally agree with you, this article is preaching a very bad practice, and is totally unrespectful to users. It’s better to not localize an application than to provide machine translated localization, which we can assert we certainty will be crap.

    • David PineMicrosoft employee Author

      The reality is that not enough applications try to provide localization. This is but a starting point, an automated way to help developers have something in place to wire up translations. I encourage developers to use this, as a tool, but then allow human review from experts in native target languages to provide edits.

      I do call this out in the summary, "Without direct intervention from a human review, the machine translations may not be conversationally...

      Read more
  • Chris Hayes

    If the promotion of the site will be handled by SEO experts, then you will not fall under sanctions, will be done competently. There are services that offer similar promotion. Look at a review of one of those buy amazon backlinks . In my opinion the most successful. At least out of those that I used.

  • Thomas Levesque

    Please, please don’t encourage machine translation for apps. Automated translations are terrible. I’m French, but my phone is set to English just to avoid the awful French translations of many apps. Sometimes I can’t even understand what something is supposed to mean, even though it’s written in my language; I often need to reverse-translate to guess what the text was in English.

    • David PineMicrosoft employee Author

      Hi Thomas, Thank you for your reply, I agree – machine-translations are just that. This is why I explicitly called out in the summary, “These are machine translations and should be treated as such. Without direct intervention from a human review, the machine translations may not be conversationally accurate or culturally specific to what you are trying to convey in the text being translated.”.

  • Frederic Forjan

    aaaa