Skip to content

Translators

ProgramX-NPledger edited this page Sep 3, 2024 · 1 revision

A number of Translators already exist in Taggloo, but there is an extensible model that provides opporutnity to add more. Each Translator can be customised to render its own output in a manner most suitable.

There are three components of a Translator:

  • ITranslatorFactory implementation, which is used to create an instance of an ITranslator.
  • ITranslator implementation, which performs the translation, returning its results in a format usable in the View.
  • The View to render the output in both summary and detail modes.

The translation functionality does not use the async/await pattern because it is designed to be called asynchronously itself, therefore the implementation of this pattern would yield little benefit.

Create a Translator

The ITranslatorFactory

Creates the implementation of an ITranslator, configuring data sources, etc. in the process. A typical ITranslatorFactory will appear similar to the IWordTranslatorFactory:

public class WordTranslatorFactory : ITranslatorFactory
{
    public ITranslator Create(DataContext entityFrameworkCoreDatabaseContext)
    {
        // create a new Translator, providing the data store context as appropriate.
        return new WordTranslator(entityFrameworkCoreDatabaseContext); 
    }
        
    public string GetTranslatorName()
    {
        // the name of the translator is used to bind configuration and allows identification for the Translator.
        return nameof(WordTranslator); 
    }   
}

Implementations of ITranslatorFactory are maintained in the Taggloo.Web.Translation.Translators.Factories namespace.

The ITranslator

The translation itself is performed by the implementation of ITranslator. Created by the ITranslatorFactory, this will have been pre-configured with necessary configuration and datastore connections and will use these to generate Translation Results.

An ITranslator is requires to implement a single function, Translate, which accepts a TranslatioNRequest that contaisn the request for the translation, whether from the API or the web site. It produces a TranslationResults object, which contains the data to eb rendered to the View.

A typical ITranslator would appear similar to the below excerpt from WordTranslator:

using API.Data;
using API.Model;
using API.Translation.Translators.Factories;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;

namespace API.Translation.Translators;

/// <summary>
/// Performs translations for Phrases.
/// </summary>
public class PhraseTranslator : ITranslator
{
    private readonly DataContext _entityFrameworkCoreDatabaseContext;

    // the constructor is called by the ITranslatorFactory and will pre-configure the ITranslator
    public PhraseTranslator(DataContext entityFrameworkCoreDatabaseContext)
    {
        _entityFrameworkCoreDatabaseContext = entityFrameworkCoreDatabaseContext;
    }

    // perform the translation, returning results
    public TranslationResults Translate(TranslationRequest translationRequest)
    {
        WordTranslation[] wordTranslations = _entityFrameworkCoreDatabaseContext.WordTranslations
            .Include(m => m.FromWord!.Dictionary)
            .Include(m => m.ToWord!.Dictionary)
            .AsNoTracking()
            .Where(q =>
                q.FromWord!.TheWord==translationRequest.Query &&
                q.FromWord!.Dictionary!.IetfLanguageTag==translationRequest.FromLanguageCode &&
                q.ToWord!.Dictionary!.IetfLanguageTag==translationRequest.ToLanguageCode
            ).OrderBy(q => q.ToWord!.TheWord)
            .Skip(translationRequest.OrdinalOfFirstResult)
            .Take(translationRequest.MaximumNumberOfResults)
            .ToArray();

        // if there were no matches and there were spaces in the query, return null
        if (!wordTranslations.Any() && translationRequest.Query.Contains((" ")))
        {
            return new TranslationResults()
            {
                ResultItems = null
            };
        }

        TranslationResults translationResults = new TranslationResults()
        {
            ResultItems = wordTranslations.Select(q => new WordTranslationResultItem()
            {
                ToWordId = q.ToWordId,
                Translation = q.ToWord?.TheWord ?? "empty",
                FromWordId = q.FromWordId
            })
        };
        
        if (translationRequest.DataWillBePaged)
        {
            // get count of available items to allow paging
            translationResults.NumberOfAvailableItemsBeforePaging = _entityFrameworkCoreDatabaseContext.WordTranslations
                .Include(m => m.FromWord!.Dictionary)
                .Include(m => m.ToWord!.Dictionary)
                .AsNoTracking()
                .Count(q => q.FromWord!.TheWord==translationRequest.Query &&
                                           q.FromWord!.Dictionary!.IetfLanguageTag==translationRequest.FromLanguageCode &&
                                           q.ToWord!.Dictionary!.IetfLanguageTag==translationRequest.ToLanguageCode);
        }

        return translationResults;
    }
}

The TranslationRequest contains the request for Translation by the user. It contains the following properties:

  • Query: the text to translate/search for
  • ClientId: If the client can be uniquely identified, the identifier of the client
  • FromLanguageCode: The IETF Language Tag being translated from
  • ToLanguageCode: The IETF Language Tag being translated to
  • OrdinalOfFirstResult: Used in paging to allow a more efficient response when getting paged items, representing the ordinal of the first item to return
  • MaximumNumberOfResults: Used in paging to allow a more efficient response when getting paged items and also to prevent a massive result-set, representing the maximum number of items to return in the result
  • DataWillBePaged

A TranslationResults object represents the results of the Translation.

If the results of the translation wouldn't be meaningful, but the request was valid (as opposed to no results), return null to indicate to the translation engine that the Translator should be disregarded in this instance.

Otherwise, the ResultItems property should be populated with the results of the translation. These results must derive from the TranslationResultIten class and will be used by the View to render the results. For example, in the case of the WordTranslator, this would be:

namespace API.Translation.Translators.Results;

public class WordTranslationResultItem : TranslationResultItem
{
    public int FromWordId { get; set; }
    
    public int ToWordId { get; set; }
}

The data returned in this object will be specific to the Translator.

The View

The View must be created in the Views/Translate folder for it to be resolved for rendering. Views must be named the same as their ITranslator type name. Views are rendered in two modes:

  • Summary mode is used as part of an overall Translation and is the first page of results users will see. This contains Translations of the query across multiple translators, eg. Words, Phrases, etc. This mode only shows up to a fixed number (eg. 5) of the first results, along with a "More" link to allow access to more results. These views are generated asynchronously using Hangfire/SignalR and are rendered to a string before being sent to the browser. Therefore, **there is no HttpContext when rendering the View`, which is why links have to be created without using the URL helpers.
  • Detail mode can be accessed from results in the Summary mode by clicking the "More" link. The Detail mode only displays for a single Translator and will provide access to all the results, using paging. The Translator View does not need to deal with paging, which is handled by the containing View.

A typical View would look like:

@using System.Web
@using API.Translation
@using API.Translation.Translators.Results
@using Microsoft.AspNetCore.Mvc.Routing
@model API.Translation.TranslationResultsWithMetaData

<div class="row">
    <div class="col-4">
        <h3><em>&quot;@Model.TranslationRequest.Query&quot;</em></h3>
    </div>
    <div class="col-8">
        @if (Model.TranslationResults.ResultItems!.Any())
        {
            <ul>
                @for (int i = 1; i <= Model.TranslationResults.MaximumItems; i++)
                {
                    TranslationResultItem resultItem = Model.TranslationResults.ResultItems!.ElementAt(i-1);
                    WordTranslationResultItem wordTranslationResultItem = (WordTranslationResultItem)resultItem;
                    <li>
                        <a href="@($"/translate/translate?Query={HttpUtility.UrlEncode(wordTranslationResultItem.Translation)}&FromLanguageCode={HttpUtility.UrlEncode(Model.TranslationRequest.ToLanguageCode)}&ToLanguageCode={HttpUtility.UrlEncode(Model.TranslationRequest.FromLanguageCode)}")">
                            @wordTranslationResultItem.Translation
                        </a>
                    </li>
                }
            </ul>
            @if (!Model.IsRenderedAsDetailsView && Model.TranslationResults.ResultItems!.Count() > 1) // Model.MaximumItemsToDisplayInTranslationSummaries)
            {
                <p class="more-results"><a href="@($"/translate/details?Query={HttpUtility.UrlEncode(Model.TranslationRequest.Query)}&FromLanguageCode={HttpUtility.UrlEncode(Model.TranslationRequest.FromLanguageCode)}&ToLanguageCode={HttpUtility.UrlEncode(Model.TranslationRequest.ToLanguageCode)}&Translators={Model.Translator}")">More &gt;</a></p>
            }
            
        }
        else
        {
            <p>There were no matches.</p>
        }
    </div>
</div>

This View is form the WordTranslator and illustrates basic concepts:

  • The TranslationResultsWithMetaData model contains the TranslationResults along with meta data about the response, for example, the TimeTaken provides a delta for the ITranslators processing time.
  • The original request is available as part of the TranslationResultsWithMetaData View Model using the TranslationRequest property. This will include the original query and language selections.
  • The results of the translation are available from the TranslationResults property, with the individual results in the ResultItems property. This will include the output of the ITranslator, each item can be cast to the more useful sub-class to be able to render appropriately.
  • The same View can be used in both Summary and Detail modes, which can be identified using the IsRenderedAsDetailsView property, which can be used to render the "More" link.

Using Translators

Resolving Translators

Translators are created and used by using the TranslationFactoryService service.

The GetTranslatorFactoriesAsync function will return the requested ITranslatorFactory items which may be used to create an ITranslator.

Configuration

Translators are configured by adding a row with the same Translator Key to the TranslatorConfiguration table. This allows basic configuration of each Translator, including whether it should be enabled.

If there is no configuration defined, the DefaultTranslationConfiguration will be utilised.

Clone this wiki locally