Software localization

Full-Stack I18n with Angular and .NET Core

Want to implement full-stack i18n with Angular and .NET Core? This guide is your go-to source for learning everything you need to know.
Software localization blog category featured image | Phrase

This tutorial will walk you through the process of internationalizing a full-stack application. We will store the product details for a car store in a database that, in turn, will store the translated data as per the locale supported by the application. We will serve our app in two languages, English and Hindi, and let the user select the app locale using a drop-down list. The app will use SQL Server as the database, .NET Core for the Web API, and Angular as the UI framework. We will use the Transloco library for i18n on the UI layer. Let's get it started!

🗒 Note » The source code is available on GitHub.

🔗 Resource » Check out our Ultimate Guide to JavaScript Localization for all the steps you need to make your JS applications accessible to international users.

Prerequisites

Before we start with our app, we need to install the following software:

  • Download the latest version of Visual Studio 2019 from the Visual Studio download page. While installing, make sure you have selected the ASP.NET and web development workload.
  • Download and install the .NET 5.0 SDK from the .NET 5.0 download page.
  • Download and install the latest version of Node.js from the Nodejs download page.
  • SQL Server 2016 or above
  • Download and install the SQL Server Management Studio (SSMS) here.
  • Install Angular CLI from this page.

Using Angular CLI is not mandatory. We are using Angular CLI for this tutorial as it provides an easy way to work with Angular applications. If you do not want to use CLI, then you need to create the files for components and services manually.

Working with the Database Layer

We will create the following two tables in the database:

  • Car: This will store the metadata for a car, such as car ID, model name, and imageURL, and
  • Car_Translations: This will store the translated data for the car name and description as per the culture supported by the application.

Run the following command to create the tables.

CREATE TABLE Car(

    Car_ID int IDENTITY(1,1) PRIMARY KEY,

    ModelName varchar(128) NOT NULL,

    ImageURL varchar(256) NOT NULL,

)

GO

CREATE TABLE Car_Translations(

    Car_ID int NOT NULL,

    Culture varchar(6) NOT NULL,

    ModelName nvarchar(200) NOT NULL,

    CarDescription nvarchar(1024) NOT NULL

)

GO

To insert the data in these tables, please refer to the DB script on GitHub.

Creating the ASP.NET Core Application

Open Visual Studio 2019, and click on “Create a new Project”. In the dialog window, select “ASP.NET Core Web Application” and click "Next." Refer to the image below.

Creating a new ASP.net project | Phrase

Now, you will be at the “Configure your new project” screen, provide ngNetCoreI18n as the name for your application, and click “Create.” Refer to the image shown below. Now you are at the “Create a new ASP.NET Core web application” screen. Select “.NET Core” and “ASP.NET Core 5.0” from the dropdowns on the top. Then, select the “ASP.NET Core with Angular” project template and click on Create. Refer to the image shown below.

Creating a new ASP.NET Core with Angular project | Phrase

This will create our project. The folder structure of the application is shown below.

Folder structure demo app | Phrase

The ClientApp folder contains the Angular code for our application. The Controllers folders will contain our API controllers. The Angular components are inside the ClientApp\src\app folder. The default template contains a few Angular components. These components will not affect our application, but for the sake of simplicity, we will delete fetchdata and counter folders from ClientApp/app/components folder. Also, remove the reference for these two components from the app.module.ts file.

Installing the NuGet Packages

We will use the Entity Framework core database first approach to scaffold our models. Therefore, we will install the current stable version of the Microsoft.EntityFrameworkCore.Tools package. Navigate to the NuGet library and copy the command shown in the “Package Manager” tab. While writing this article, the current stable version is 5.0.2.

To install the package, navigate to Tools > NuGet Package Manager > Package Manager Console. It will open the Package Manager Console. Run the command as shown below.

Install-Package Microsoft.EntityFrameworkCore.Tools -Version 5.0.2

We will also need the package for the database provider that we are targeting, SQL Server in this case. Therefore, we will install the Microsoft.EntityFrameworkCore.SqlServer package. Run the command as shown below.

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.2

Scaffolding the Model for the Application Using EF Core

To scaffold the model from our DB tables, run the following command in the Package Manager Console.

Scaffold-DbContext "Put the connection string here" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Car, Car_Translations

Do not forget to use your connection string (inside the quotation marks). After this command gets executed successfully, you can observe a Models folder has been created. The Models folder will have three class files Car.cs, CarTranslation.cs, and CarDBContext.cs. The name of the DB Context class will be the name of the database suffixed with the word “Context.” Here, the name of the database is “Car,” that is why the DB context class name is “CarDBContext.”

🗒 Note » If you want to find out how to set a connection string for your SQL Server instance, please refer to get connection string for SQL Server.

We will add a DTO (Data Transfer Object) class to facilitate the data transfer between the API and the UI layer. Right-click on the ngNetCoreI18n project and select Add > New Folder. Set “Dto” as the name of the folder. Right-click again on the “Dto” folder and select Add > Class to add a new class file. Put the name of the class as CarDto.cs and click “Add.” Put the following code inside the class:

public class CarDto

{

    public int CarId { get; set; }

    public string ModelName { get; set; }

    public string ImageUrl { get; set; }

    public string CarDescription { get; set; }

}

Configuring the Connection String

While scaffolding the model, EF Core will place the connection string inside the CarDBContext class. However, it is not a good practice to keep the connection string inside the DB Context class. The compiler will also show a warning. Therefore, we will move the connection string to the appsettings.json file.

Open the appsettings.json file and put the following code inside it.

"ConnectionStrings": {

  "DefaultConnection": "Your connection string here"

},

We will register the DB Context class using Dependency Injection (DI) in the Startup class. Add the following line in the ConfigureServices method of the Startup.cs file.

services.AddDbContext<CarDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));

To learn more about DI, please refer to Dependency injection in .NET.

Creating the Interface

Add a new folder called “Interfaces”. Add a new class in this folder and name it “Interfaces.” Put the following code in the ICar.cs file.

public interface ICar

{

    IEnumerable<CarDto> GetAllCarData(string culture);

}

We will implement this interface in the data access layer of our application. The GetAllCarData method will accept the culture as a parameter.

Creating Data Access Layer for the Application

Add a new folder to the project and name it “DataAccess.” Add a new class in this folder and name it CarDataAccessLayer.

Add the following code in the CarDataAccessLayer.cs file.

public class CarDataAccessLayer : ICar

{

    private CarDBContext db;

    public CarDataAccessLayer(CarDBContext _db)

    {

        db = _db;

    }

    public IEnumerable<CarDto> GetAllCarData(string culture)

    {

        List<CarDto> lstCar = new();

        List<Car> carDetails = db.Cars?.ToList();

        foreach (Car car in carDetails)

        {

            CarDto item = new();

            var carData = db.CarTranslations.FirstOrDefault(

                                x => x.CarId == car.CarId &&

                                x.Culture == culture);

            if (carData != null)

            {

                item.CarId = car.CarId;

                item.ModelName = carData?.ModelName;

                item.ImageUrl = car.ImageUrl;

                item.CarDescription = carData?.CarDescription;

                lstCar.Add(item);

            }

        }

        return lstCar;

    }

}

We have injected the DBContex class into the CarDataAccessLayer class with the help of the .NET Core DI services. The GetAllCarData method will fetch the details of all the cars from the database translated into the given culture.

Add the following line in the ConfigureServices method of the Startup.cs file.

services.AddTransient<ICar, CarDataAccessLayer>();

Adding the Car Controller

We will add a Car Controller to our application, which will handle the API request from the UI layer. Right-click on the Controllers folder and select Add > New Item. In the “Add New Item” dialog box, select “Visual C#” from the left panel, then select “API Controller-Empty” from the templates panel, and set the name CarController.cs. Click “Add.” Refer to the image below.

Adding an API Controller | Phrase

Put the following code in the CarController.cs file.

[Route("api/{culture}/[controller]")]

[ApiController]

public class CarController : ControllerBase

{

    readonly ICar carService;

    public CarController(ICar _carService)

    {

        carService = _carService;

    }

    [HttpGet]

    public IEnumerable<CarDto> Get(string culture)

    {

        return carService.GetAllCarData(culture);

    }

}

The controller will accept the culture as the API parameter. This parameter will be passed from the UI layer based on the culture selected by the user. The Get method will return the list of all the car data based on the culture value.

Working with the Client Side of the App

We will use the Transloco library to implement i18n in Angular. Navigate to the ngNetCoreI18n\ClientApp folder and open a command-line window. Run the following command to install Transloco.

ng add @ngneat/transloco

After running this command, you get two questions. Answer them as shown below:

  • Which languages do you need? (en, hi)
  • Are you working with server-side rendering? (N)

After the successful execution of the command, a folder called “i18n” with two JSON files, en.json and hi.json, will be created in the assets folder. At this point, these files are empty. We will add the translations to them later on. The command will also create a new file called transloco-root.module.ts, and inject it into the AppModule.

Run the following command to install ngx-bootstrap for easy styling and UI elements.

ng add ngx-bootstrap

We will use the dropdown module of ngx-bootstrap to display a language selection dropdown menu in the navbar of our app. For this purpose, add the following lines of code in the ClientApp/src/app/app.module.ts file to import the required module.

import { BsDropdownModule } from 'ngx-bootstrap/dropdown';

@NgModule({

  ...

  imports: [

    ...

    BsDropdownModule.forRoot(),

  ],

    ...

})

To learn more about using Transloco with an Angular application, please refer to Angular 10 Tutorial on Localization with Transloco.

Create the Models

Create a folder called models inside the ClientApp\src\app folder. Now, we will create a file car.ts in the models folder. Put the following code in it.

export class Car {

  carId: number;

  modelName: string;

  imageUrl: string;

  carDescription: string;

}

Creating the Car Service

We will create an Angular service to invoke the API endpoints. Run the following command.

ng g s services\car

This command will create a folder with name services and then create the following two files inside it.

  • service.ts – the service class file,
  • service.spec.ts – the unit test file for the service.

Open the car.service.ts file and put the following code inside it.

export class CarService {

  public currentCulture = new BehaviorSubject<string>('en');

  constructor(private http: HttpClient) {}

  getProductDetails(culture: string) {

    return this.http.get<Car[]>(`/api/${culture}/car`);

  }

}

We have created a multicast observable called currentCulture, which is used to store the selected culture of the application. The default language of the application is set to English in the transloco-root.module.ts file. Therefore, we will set the initial value of the culture observer to “en”. The getProductDetails function will hit the API endpoint and fetch the details of the car from the database.

Creating the nav-bar Component

Run the following command to create the nav-bar component for our application.

ng g c nav-bar

Open src\app\nav-bar\nav-bar.component.html, and replace what you see there with the following code.

<nav class="navbar navbar-dark navbar-expand-lg">

  <a class="navbar-brand">Phrase</a>

  <span class="spacer"></span>

  <div class="btn-group" dropdown>

    <button id="button-animated" dropdownToggle type="button" class="btn btn-link dropdown-toggle"

      aria-controls="dropdown-animated">

      <i class="fa fa-globe" aria-hidden="true"></i> {{ siteLanguage }}

      <span class="caret"></span>

    </button>

    <ul id="dropdown-animated" *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu"

      aria-labelledby="button-animated">

      <ng-container *ngFor="let language of languageList">

        <li role="menuitem">

          <a class="dropdown-item" (click)="changeSiteLanguage(language.code)">{{language.label}}</a>

        </li>

      </ng-container>

    </ul>

  </div>

</nav>

We have created a nav-bar that contains a drop-down menu having two options for setting the language of our app. When we click on the menu item, it will invoke the changeSiteLanguage method that will change the app language and the content will be served in the selected language. We will also display the siteLanguage in the nav-bar and it will update as we change the language of our app.

Open src\app\nav-bar\nav-bar.component.ts, and replace the existing code with the following code.

import { Component } from '@angular/core';

import { BsDropdownConfig } from 'ngx-bootstrap/dropdown';

import { TranslocoService } from '@ngneat/transloco';

import { CarService } from '../services/car.service';

@Component({

  selector: 'app-nav-bar',

  templateUrl: './nav-bar.component.html',

  styleUrls: ['./nav-bar.component.scss'],

  providers: [

    {

      provide: BsDropdownConfig,

      useValue: { isAnimated: true, autoClose: true },

    },

  ],

})

export class NavBarComponent {

  siteLanguage = 'English';

  languageList = [

    { code: 'en', label: 'English' },

    { code: 'hi', label: 'Hindi' },

  ];

  constructor(

    private carService: CarService,

    private translocoService: TranslocoService

  ) {}

  changeSiteLanguage(language: string): void {

    this.translocoService.setActiveLang(language);

    this.carService.currentCulture.next(language);

    this.siteLanguage = this.languageList.find(

      (f) => f.code === language

    ).label;

  }

}

Here, we have defined a list of languages and their standard locale codes. The variable siteLanguage is used to hold the current active culture of the application.

The changeSiteLanguage method will invoke the setActiveLang method of the Transloco service to set the active language of the app to the language selected in the nav-bar menu. We will then set the siteLanguage by fetching the language name from the list of languages corresponding to the currently active language. We will also update the currentCulture observable to the selected language value. Doing so will allow us to notify other components about the current culture of the application. This will be helpful in fetching the car data corresponding to the current application culture. We will explore this in the next section when we create the car component.

You can refer to the CSS file on the GitHub to set the styling for this component.

Creating the Car Component

Run the following command to create the car component.

ng g c car

Open src\app\car\car.component.ts, and put the following code inside the CarComponent class:

public carData: Car[];

private culture: string;

constructor(private carService: CarService) { }

ngOnInit(): void {

  this.carService.currentCulture.subscribe((currentCulture) => {

    this.culture = currentCulture;

    this.getProductDetails();

  });

}

getProductDetails() {

  this.carService

    .getProductDetails(this.culture)

    .subscribe((carData) => (this.carData = carData));

}

Inside the ngOnInit method, we will subscribe to the currentCulture observable to fetch the current culture of the application. The getProductDetails method will invoke the method from the service to fetch the Car data.

Open src\app\car\car.component.html, and replace what you see there with the following code

<h1 class="display-4">{{ "page.title" | transloco }}</h1>

<p class="lead">

  {{ "page.subtitle" | transloco }}

</p>

<hr />

<div class="card-deck">

  <div class="card" *ngFor="let car of carData">

    <img class="card-img-top" src="{{ car.imageUrl }}" alt="car image" />

    <div class="card-body">

      <h5 class="card-title">{{ car.modelName }}</h5>

      <hr />

      <p class="card-text">

        {{ car.carDescription }}

      </p>

    </div>

  </div>

</div>

We will display the car data in a card layout, created with the help of Bootstrap. We are using the transloco pipe to read the key of the title and the subtitle field from the translation file. While running the application, these keys will be replaced by their corresponding values from the selected language JSON file.

Adding Translations

We will add the translations for the page title and subtitle. Open the src\assets\i18n\en.json file and put the following code inside it.

{

  "page": {

    "title": "Mercedes-Benz",

    "subtitle": "A list of passenger cars by Mercedes. Explore our online store and find your suitable ride."

  }

}

Similarly, add the following code in the src\assets\i18n\hi.json file

{

  "page": {

    "title": "मर्सिडीज बेंज",

    "subtitle": "मर्सिडीज द्वारा यात्री कारों की एक सूची। हमारे ऑनलाइन स्टोर का अन्वेषण करें और अपनी उपयुक्त सवारी ढूंढें।"

  }

}

Execution Demo

Run the code from Visual Studio. The app will be launched in the default browser. Select the language value from the drop-down in the nav-bar and the site content will be delivered in the selected language. Refer to the image shown below:

Localized demo app | Phrase

Extend The App by Adding a New Language

We have designed our app in such a manner that it is easy to add support for a new language with minimal code changes. Let us assume, you want to add support for German in this app, then follow the steps as mentioned below:

  • Add the translations for the German language in the Car_Translations table,
  • Add the language code for German i.e. de, inside the availableLangs array in the transloco-root.module.ts file,
  • Add the entry for the German language in the languageList variable inside the NavBarComponent class,
  • Add a new file, de.json inside the src\assets\i18n folder. Add the German translation for the page title and subtitle inside this file.

Wrapping Up The Full-Stack I18n Tutorial

In this tutorial, we explored how to implement i18n in a full-stack app created using .NET, SQL Server, and Angular. We served the app in two languages – English and Hindi. The database was used to store the translated content. The user could change the culture during runtime, and the web API would fetch the data based on the selected culture of the application.

Finally, if you’re looking for a reliable partner in localizing software for global markets, Phrase is the way to go! Sign up for a 14-day trial, and see what the leanest, fastest, and most reliable localization platform on the market can do for you.