Azure AI and ASP.NET Core: Building an AI-Powered Language Translator with Azure Translator Service, and GPT-4o

In this article, we will build a complete ASP.NET Core 9 Razor View (MVC) application that accepts text or document files (.txt, .doc, .docx), auto-detects the source language using Azure AI Translator Service, lets the user choose a target language, and performs a meaningful, context-aware translation using Azure OpenAI GPT-4o. The application also features a real-time progress bar powered by SignalR that shows actual server-side step progression.

What is Azure Translator Service?

Azure Translator Service (part of Azure AI Services, formerly Cognitive Services) is a cloud-based Neural Machine Translation (NMT) service provided by Microsoft. It enables developers to add multi-language translation capabilities to their applications through a simple REST API or SDK.

It offers the following core capabilities:

  • Text Translation: Translate text between 100+ languages and dialects in real-time.
  • Language Detection: Automatically detect the language of any input text without the user needing to specify it.
  • Transliteration: Convert text from one script to another (e.g., Hindi Devanagari to Latin script).
  • Dictionary Lookup: Get alternative translations and usage examples for individual words.
  • Document Translation: Translate entire documents while preserving their original formatting.
  • Custom Translator: Train custom models tailored to your domain-specific terminology (e.g., medical, legal, technical).

Advantages of Azure Translator Service

Advantage Description
100+ Languages Supports over 100 languages and dialects out of the box, covering the vast majority of the world's population.
Neural Machine Translation Uses advanced neural networks trained on millions of sentence pairs, delivering significantly better quality than older statistical methods.
Fast & Low-Cost Extremely fast response times (milliseconds) and a generous free tier (2 million characters/month). Far cheaper than LLM-based translation for high-volume use.
Auto Language Detection Can automatically identify the source language without user input — essential for multi-language applications.
Enterprise-Grade Security Data is encrypted in transit and at rest. Microsoft does not store your translation data. Fully GDPR, HIPAA, and SOC 2 compliant.
Custom Translator Train custom translation models with your own domain-specific data for higher accuracy in specialized fields (legal, medical, technical).
Simple SDK Integration Official .NET SDK (Azure.AI.Translation.Text) with strongly-typed models, making integration into ASP.NET Core applications straightforward.

Why Use GPT-4o If We Already Have Azure Translator Service?

This is the most important design question in our application. Azure Translator Service is excellent at what it does is a fast, affordable, and accurate for straightforward translations. But it has limitations that GPT-4o overcomes:

Aspect Azure Translator (NMT) GPT-4o (LLM)
Translation Approach Sentence-by-sentence neural translation. Treats each sentence somewhat independently. Understands the entire document context. Translates with awareness of surrounding paragraphs, tone, and intent.
Idiomatic Expressions Often translates idioms literally. "It's raining cats and dogs" may produce a confusing literal translation. Understands idioms and produces the equivalent expression in the target language, preserving the intended meaning.
Tone & Formality Cannot adjust tone. Produces a single "neutral" translation regardless of context. Can be instructed to preserve formal, informal, humorous, or professional tone via the system prompt.
Ambiguity Handling Picks one translation for ambiguous words without considering broader context. Uses full-document context to resolve ambiguity correctly. E.g., "bank" as financial institution vs. river bank.
Technical / Domain Text Requires custom-trained models for domain-specific accuracy. GPT-4o has been trained on vast domain knowledge and handles technical, legal, medical text well out of the box.
Cost Very cheap — $10 per million characters. Free tier: 2M chars/month. More expensive — token-based pricing. Best used for quality-critical translations.
Speed Extremely fast (milliseconds). Slower (seconds) — depends on text length and model load.

Our Hybrid Approach: Best of Both Worlds

In this application, we use both services together in a complementary way:

  1. Azure Translator Service handles language detection and provides the list of supported languages. It is fast, free (within the free tier), and purpose-built for these tasks.
  2. GPT-4o handles the actual translation. Since this is the step where quality matters most — preserving meaning, tone, context, idioms, and domain-specific terminology — GPT-4o's deep language understanding delivers significantly better results than a standard NMT engine.

This hybrid architecture gives us the speed and affordability of Azure Translator for detection, combined with the translation quality of GPT-4o for the final output — the best of both worlds.

The Architecture flow of the application is shown in Figure 1

Figure 1: The Application Flow Diagram

What We Will Build

  • Step 1: User enters text or uploads a file (.txt, .doc, .docx — max 5 MB)
  • Step 2: Azure Translator Service detects the source language. User selects target language from a dropdown.
  • Step 3: GPT-4o performs a meaningful, context-aware translation. Results are displayed side-by-side.
  • Real-time Progress: SignalR pushes actual step-by-step progress from the server to the browser.

Prerequisites to Implement the Application

  1. .NET 9 SDK installed
  2. An Azure Translator Service resource (Cognitive Services) — note down the Endpoint, API Key, and Region
  3. An Azure OpenAI Service resource with a GPT-4o deployment — note down the Endpoint, API Key, and Deployment Name
  4. Visual Studio 2022 / VS Code / any editor of your choice

Step 1: Create the ASP.NET Core 9 MVC Project

Open a terminal and run:

dotnet new mvc -n TranslatorApp --framework net9.0
cd TranslatorApp

Step 2: Install NuGet Packages

We need three NuGet packages:

dotnet add package Azure.AI.Translation.Text
dotnet add package Azure.AI.OpenAI
dotnet add package DocumentFormat.OpenXml
Package Purpose
Azure.AI.Translation.Text Language detection & supported language list via Azure Translator
Azure.AI.OpenAI GPT-4o model access for meaningful translation
DocumentFormat.OpenXml Extract text from .doc/.docx Word documents

Step 3: Configure appsettings.json

Add your Azure service keys and endpoints:

{
  "AzureTranslator": {
    "Endpoint": "https://YOUR-TRANSLATOR.cognitiveservices.azure.com/",
    "ApiKey": "YOUR_AZURE_TRANSLATOR_KEY",
    "Region": "eastus"
  },
  "AzureOpenAI": {
    "Endpoint": "https://YOUR-OPENAI.openai.azure.com/",
    "ApiKey": "YOUR_AZURE_OPENAI_KEY",
    "DeploymentName": "gpt-4o"
  }
}

  The appsettings.json for keeping Keys, Use Azure KeyVault In Production-Ready applications

Step 4: Create the ViewModel

Create Models/TranslationViewModel.cs. This ViewModel carries all state across the 3-step workflow (Input → Detect → Translate).

namespace TranslatorApp.Models;

/// ViewModel that carries all state between the Razor view and the controller
/// across the 3-step translation workflow: Input -> Detect -> Translate.
public class TranslationViewModel
{
    /// The raw text entered by the user or extracted from an uploaded file.
    public string? InputText { get; set; }

    /// The human-readable name of the detected source language (e.g., "English").
    public string? DetectedLanguage { get; set; }

    /// The ISO language code of the detected source language (e.g., "en", "hi").
    public string? DetectedLanguageCode { get; set; }

    /// The ISO language code of the target language selected by the user.
    public string? TargetLanguageCode { get; set; }

    /// The final translated text returned by GPT-4o.
    public string? TranslatedText { get; set; }

    /// Error message to display when any step fails.
    public string? ErrorMessage { get; set; }

    /// The name of the uploaded file (if any).
    public string? FileName { get; set; }

    /// List of all languages supported by Azure Translator.
    public List<SupportedLanguage> AvailableLanguages { get; set; } = [];

    /// Flag indicating whether language detection has been completed.
    public bool IsDetected { get; set; }
}

public class SupportedLanguage
{
    public string Code { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
}

Step 5: Create the File Extractor Service

This service extracts plain text from uploaded .txt, .doc, and .docx files. It uses DocumentFormat.OpenXml to read Word documents and StreamReader for plain text.

5a. Interface — Services/IFileExtractorService.cs

namespace TranslatorApp.Services;

public interface IFileExtractorService
{
    /// Reads the uploaded file stream and extracts its text content
    /// based on the file extension.
    Task<string> ExtractTextAsync(Stream fileStream, string fileName);
}

5b. Implementation — Services/FileExtractorService.cs

using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace TranslatorApp.Services;

public class FileExtractorService : IFileExtractorService
{
    /// Routes the file to the appropriate extraction method based on extension.
    public async Task<string> ExtractTextAsync(Stream fileStream, string fileName)
    {
        var extension = Path.GetExtension(fileName).ToLowerInvariant();

        return extension switch
        {
            ".txt" => await ExtractFromTxtAsync(fileStream),
            ".doc" or ".docx" => ExtractFromDocx(fileStream),
            _ => throw new NotSupportedException(
                $"File type '{extension}' is not supported.")
        };
    }

    /// Reads a .txt file using UTF-8 encoding.
    private static async Task<string> ExtractFromTxtAsync(Stream stream)
    {
        using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true);
        return await reader.ReadToEndAsync();
    }

    /// Extracts text from a Word document (.doc/.docx) using OpenXml SDK.
    /// Iterates through all Paragraph elements and concatenates their inner text.
    private static string ExtractFromDocx(Stream stream)
    {
        // Copy to MemoryStream because OpenXml requires a seekable stream
        using var memoryStream = new MemoryStream();
        stream.CopyTo(memoryStream);
        memoryStream.Position = 0;

        using var wordDocument = WordprocessingDocument.Open(memoryStream, false);
        var body = wordDocument.MainDocumentPart?.Document?.Body;
        if (body == null) return string.Empty;

        var sb = new StringBuilder();
        foreach (var paragraph in body.Elements<Paragraph>())
        {
            sb.AppendLine(paragraph.InnerText);
        }
        return sb.ToString().Trim();
    }
}

Step 6: Create the Translation Service

This is the core service that integrates Azure Translator (for language detection) and Azure OpenAI GPT-4o (for meaningful translation).

6a. Interface — Services/ITranslationService.cs

using TranslatorApp.Models;

namespace TranslatorApp.Services;

public interface ITranslationService
{
    Task<(string languageName, string languageCode)> DetectLanguageAsync(string text);
    Task<List<SupportedLanguage>> GetSupportedLanguagesAsync();
    Task<string> TranslateWithGptAsync(string text, string sourceLanguage, string targetLanguage);
}

6b. Implementation — Services/TranslationService.cs

using Azure;
using Azure.AI.Translation.Text;
using Azure.AI.OpenAI;
using OpenAI.Chat;
using TranslatorApp.Models;

namespace TranslatorApp.Services;

public class TranslationService : ITranslationService
{
    private readonly TextTranslationClient _translatorClient;
    private readonly AzureOpenAIClient _openAIClient;
    private readonly string _gptDeploymentName;

    /// Constructor: Reads Azure configuration from appsettings.json
    /// and initializes both SDK clients.
    public TranslationService(IConfiguration configuration)
    {
        // Azure Translator configuration
        var translatorKey = configuration["AzureTranslator:ApiKey"]!;
        var translatorRegion = configuration["AzureTranslator:Region"]!;
        var translatorEndpoint = configuration["AzureTranslator:Endpoint"]!;

        _translatorClient = new TextTranslationClient(
            new AzureKeyCredential(translatorKey),
            new Uri(translatorEndpoint),
            translatorRegion);

        // Azure OpenAI configuration
        var openAIEndpoint = configuration["AzureOpenAI:Endpoint"]!;
        var openAIKey = configuration["AzureOpenAI:ApiKey"]!;
        _gptDeploymentName = configuration["AzureOpenAI:DeploymentName"]!;

        _openAIClient = new AzureOpenAIClient(
            new Uri(openAIEndpoint),
            new AzureKeyCredential(openAIKey));
    }

    /// Detects the language using Azure Translator's TranslateAsync
    /// with auto-detection (no source language specified).
    /// Only the first 500 characters are sent for efficiency.
    public async Task<(string, string)> DetectLanguageAsync(string text)
    {
        var snippet = text.Length > 500 ? text[..500] : text;
        var response = await _translatorClient.TranslateAsync("en", snippet);
        var result = response.Value.FirstOrDefault();

        if (result?.DetectedLanguage == null)
            throw new InvalidOperationException("Could not detect language.");

        var detectedCode = result.DetectedLanguage.Language;
        var languages = await _translatorClient.GetSupportedLanguagesAsync();
        var langName = detectedCode;

        if (languages.Value.Translation.TryGetValue(detectedCode, out var langInfo))
            langName = langInfo.Name;

        return (langName, detectedCode);
    }

    /// Fetches all supported languages from Azure Translator, sorted by name.
    public async Task<List<SupportedLanguage>> GetSupportedLanguagesAsync()
    {
        var response = await _translatorClient.GetSupportedLanguagesAsync();
        return response.Value.Translation
            .Select(kvp => new SupportedLanguage { Code = kvp.Key, Name = kvp.Value.Name })
            .OrderBy(l => l.Name).ToList();
    }

    /// Translates text using GPT-4o for accurate, meaningful translation.
    /// A system prompt instructs GPT-4o to act as a professional translator.
    public async Task<string> TranslateWithGptAsync(
        string text, string sourceLanguage, string targetLanguage)
    {
        var chatClient = _openAIClient.GetChatClient(_gptDeploymentName);
        var messages = new List<ChatMessage>
        {
            new SystemChatMessage(
                $"You are a professional translator. Translate the following text " +
                $"from {sourceLanguage} to {targetLanguage}. " +
                "Provide only the translated text without any explanations. " +
                "Preserve the original meaning, tone, and context."),
            new UserChatMessage(text)
        };

        var response = await chatClient.CompleteChatAsync(messages);
        return response.Value.Content[0].Text;
    }
}

Important Key Design Decision: The Azure Translator SDK does not expose a standalone DetectLanguageAsync() method. We use TranslateAsync("en", text) with no source language  this triggers auto-detection. The DetectedLanguage property on the response gives us the detected language code.

Step 7: Create the SignalR Progress Hub, to show the real-time translation progress on UI

The ProgressHub is a minimal SignalR hub. The controller pushes progress updates to specific clients using IHubContext<ProgressHub>.

using Microsoft.AspNetCore.SignalR;

namespace TranslatorApp.Hubs;

/// SignalR hub that pushes real-time progress updates from the server to the client.
/// The controller uses IHubContext to send step-by-step progress
/// (percentage + message) to the specific client by connectionId.
public class ProgressHub : Hub
{
}

Step 8: Create the HomeController

The controller orchestrates the entire workflow. It injects ITranslationService, IFileExtractorService, and IHubContext<ProgressHub>. After each real processing step, it pushes a progress update to the client via SignalR.

Progress Steps — Detect Action

%Server-Side Step
10%Validating input...
30%Extracting text from file...
60%Detecting language using Azure Translator Service...
90%Fetching supported languages...
100%Language detected successfully!

Progress Steps — Translate Action

%Server-Side Step
10%Preparing source text...
30%Resolving target language...
50%Translating to {language} using GPT-4o model...
100%Translation completed successfully!
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using TranslatorApp.Hubs;
using TranslatorApp.Models;
using TranslatorApp.Services;

namespace TranslatorApp.Controllers;

public class HomeController : Controller
{
    private readonly ITranslationService _translationService;
    private readonly IFileExtractorService _fileExtractorService;
    private readonly IHubContext<ProgressHub> _hubContext;
    private const long MaxFileSize = 5 * 1024 * 1024; // 5 MB
    private static readonly string[] AllowedExtensions = [".txt", ".doc", ".docx"];
    private const string TempDataTextKey = "InputText";

    /// Sends a real-time progress update to a specific client via SignalR.
    private async Task SendProgressAsync(string? connectionId, int pct, string msg)
    {
        if (!string.IsNullOrEmpty(connectionId))
            await _hubContext.Clients.Client(connectionId)
                .SendAsync("ReceiveProgress", pct, msg);
    }

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Detect(
        string? inputText, IFormFile? file, string? connectionId)
    {
        var model = new TranslationViewModel();
        try
        {
            await SendProgressAsync(connectionId, 10, "Validating input...");
            // ... validate file size and type ...

            await SendProgressAsync(connectionId, 30, "Extracting text from file...");
            // ... extract text from file or use inputText ...

            await SendProgressAsync(connectionId, 60,
                "Detecting language using Azure Translator Service...");
            var (langName, langCode) = await _translationService.DetectLanguageAsync(text);

            await SendProgressAsync(connectionId, 90, "Fetching supported languages...");
            model.AvailableLanguages = await _translationService.GetSupportedLanguagesAsync();

            await SendProgressAsync(connectionId, 100, "Language detected successfully!");
        }
        catch (Exception ex) { /* handle error */ }
        return View("Index", model);
    }

    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Translate(
        TranslationViewModel model, string? connectionId)
    {
        try
        {
            await SendProgressAsync(connectionId, 10, "Preparing source text...");
            // ... retrieve text from TempData ...

            await SendProgressAsync(connectionId, 30, "Resolving target language...");
            // ... resolve language code to name ...

            await SendProgressAsync(connectionId, 50,
                $"Translating to {targetLangName} using GPT-4o model...");
            model.TranslatedText = await _translationService.TranslateWithGptAsync(
                model.InputText, model.DetectedLanguage, targetLangName);

            await SendProgressAsync(connectionId, 100, "Translation completed!");
        }
        catch (Exception ex) { /* handle error */ }
        return View("Index", model);
    }
}

Why do we use the TempData Pattern here?

 File-extracted text is stored in TempData (backed by session) during Detect and retrieved in Translate. This avoids the issue where <input type="hidden"> corrupts multi-line text containing newlines and special characters.

Step 9: Create the Razor View (Index.cshtml)

The view is divided into three card sections controlled by the IsDetected and TranslatedText flags. It includes a SignalR client that connects to /progressHub and listens for ReceiveProgress events to animate the progress bar in real-time.

Important Key View Features:

  • Step 1 Card: Text area + file upload form. Hidden field carries the SignalR connectionId.
  • Step 2 Card: Detected language badge, source text preview, target language dropdown, and Translate button.
  • Step 3 Card: Side-by-side comparison of original and translated text.
  • Progress Bar: Hidden by default. On form submit, JavaScript shows it. SignalR events update the width, percentage, and message in real-time.

SignalR Client JavaScript (key section):

// Connect to the ProgressHub on the server
var connection = new signalR.HubConnectionBuilder()
    .withUrl("/progressHub")
    .build();

// Listen for real-time progress updates from the controller.
// Each update has the actual percentage and step message.
connection.on("ReceiveProgress", function (percentage, message) {
    var bar = document.getElementById('progressBar');
    bar.style.width = percentage + '%';
    document.getElementById('progressMessage').textContent = message;
    document.getElementById('progressPercent').textContent = percentage + '%';
});

// Start the connection and store the connectionId in the hidden form field.
// The controller uses this connectionId to push progress to THIS browser tab.
connection.start().then(function () {
    var connId = connection.connectionId;
    document.getElementById('detectConnectionId').value = connId;
    document.getElementById('translateConnectionId').value = connId;
});

Step 10: Configure Program.cs to Register Services and SignalR

Register MVC, Session, SignalR, and our custom services in the DI container. Map the SignalR hub endpoint.

using TranslatorApp.Hubs;
using TranslatorApp.Services;

var builder = WebApplication.CreateBuilder(args);

// Register MVC services (controllers + Razor views)
builder.Services.AddControllersWithViews();

// Register session — used by TempData to persist extracted text between POST requests
builder.Services.AddSession();

// Register SignalR — used by ProgressHub to push real-time progress to the client
builder.Services.AddSignalR();

// Register custom services as singletons (Azure SDK clients are thread-safe)
builder.Services.AddSingleton<ITranslationService, TranslationService>();
builder.Services.AddSingleton<IFileExtractorService, FileExtractorService>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseRouting();
app.UseSession();
app.UseAuthorization();
app.MapStaticAssets();

// Map SignalR hub — client connects to /progressHub
app.MapHub<ProgressHub>("/progressHub");

// Map default MVC route
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();

app.Run();

Step 11: Install the SignalR JavaScript Client

Download the SignalR client library into the wwwroot/lib folder:

mkdir -p wwwroot/lib/microsoft-signalr
curl -sL "https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js" \
     -o wwwroot/lib/microsoft-signalr/signalr.min.js

Then reference it in your view:

<script src="~/lib/microsoft-signalr/signalr.min.js"></script>

Step 12: Run the Application

dotnet run

Navigate to https://localhost:5001 (or the port shown in the console). You should see the translator interface.

How the Real-Time Progress Works with JavaScript and ASP.NET Core Applications?

  1. When the page loads, the SignalR JavaScript client connects to /progressHub and receives a unique connectionId.
  2. This connectionId is stored in a hidden form field.
  3. When the user clicks Detect or Translate, the form POSTs the connectionId along with the data.
  4. The controller receives the connectionId and uses IHubContext<ProgressHub> to push progress updates (percentage + message) to that specific client after each real processing step completes.
  5. The JavaScript ReceiveProgress handler updates the progress bar width, percentage badge, and message text in real-time.
  6. When the server responds with the full page (form POST completes), the page reloads with the results.

Summary of Implementation

Component Technology Role
Web Framework ASP.NET Core 9 MVC Razor Views + Controller
Language Detection Azure Translator Service Auto-detect source language
Translation Engine Azure OpenAI GPT-4o Meaningful, context-aware translation
File Processing DocumentFormat.OpenXml Extract text from .doc/.docx
Real-Time Progress SignalR Push server-side step progress to browser
UI Framework Bootstrap 5 Responsive cards, progress bar, badges

Conclusion: This application demonstrates how to combine Azure AI services with ASP.NET Core 9 to build a production-ready translator that handles both text and document input, provides accurate GPT-4o translations, and keeps the user informed with real-time SignalR progress updates.

Code for this article can be donwloadedform this link

The Following Video shows the Result:



 

Popular posts from this blog

ASP.NET Core 8: Creating Custom Authentication Handler for Authenticating Users for the Minimal APIs

ASP.NET Core 7: Using PostgreSQL to store Identity Information

Azure AI Document Intelligence: Processing an Invoice and Saving it in Azure SQL Server Database using Azure Functions