Translation server side
This file explains what to add the translation on some fields on an existing entity.
Prerequisite
Knowledge to have:
Within the framework of the BIA, the chosen approach is Separate translation table approach : In this approach Instead of putting all translation under the same table, we will write a separate translation table for each table which requires localization.

Implementation
Back
-
Create the translation table entity in Domain/TranslationModule/Aggregate.
- it should contain :
- an id
- a link on language (Language + LanguageId)
- a link on the entity to translate (Notification + NotificationId in this example)
- the fields to translate (Title + Description in this example).
/// <summary>/// The role entity./// </summary>public class NotificationTranslation : VersionedTable, IEntity{/// <summary>/// Gets or sets the id./// </summary>public int Id { get; set; }/// <summary>/// Gets or sets the language./// </summary>public Language Language { get; set; }/// <summary>/// Gets or sets the language id./// </summary>public int LanguageId { get; set; }/// <summary>/// Gets or sets the notification type./// </summary>public Notification Notification { get; set; }/// <summary>/// Gets or sets the notification type id./// </summary>public int NotificationId { get; set; }/// <summary>/// Gets or sets the title translated./// </summary>public string Title { get; set; }/// <summary>/// Gets or sets the description translated./// </summary>public string Description { get; set; }}
- it should contain :
-
Add the function to define the model with constraint (and Data if required) in Infrastructure.Data/ModelsBuilders/TranslationModelBuilder.cs
- it should contain :
- the HasKey(r => r.Id)
- the required constraint on LanguageId and the entity to translate id (NotificationId in this example)
- the HasIndex on those 2 keys (to avoid duplication of translation on same language and same entity)
- the constraint on fields to translate. it should be identical to the constraints in the entity required or not, length... (Title + Description in this example).
/// <summary>/// Create the model for notification./// </summary>/// <param name="modelBuilder">The model builder.</param>private static void CreateNotificationTranslationModel(ModelBuilder modelBuilder){modelBuilder.Entity<NotificationTranslation>().HasKey(r => r.Id);modelBuilder.Entity<NotificationTranslation>().Property(r => r.NotificationId).IsRequired();modelBuilder.Entity<NotificationTranslation>().Property(r => r.LanguageId).IsRequired();modelBuilder.Entity<NotificationTranslation>().HasIndex(u => new { u.NotificationId, u.LanguageId }).IsUnique();modelBuilder.Entity<NotificationTranslation>().Property(m => m.Title).IsRequired().HasMaxLength(100);modelBuilder.Entity<NotificationTranslation>().Property(m => m.Description).IsRequired().HasMaxLength(256);} - in case of data to enter at migration add the translation in all language in this function:
- increment the id of 100 when changing of entity (in case of adding language in the future)
- example of adding notification Type translation in French, spanish and german.
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 1, LanguageId = LanguageId.French, Id = 101, Label = "Tâche" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 1, LanguageId = LanguageId.Spanish, Id = 102, Label = "Tarea" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 1, LanguageId = LanguageId.German, Id = 103, Label = "Aufgabe" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 2, LanguageId = LanguageId.French, Id = 201, Label = "Information" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 2, LanguageId = LanguageId.Spanish, Id = 202, Label = "Información" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 2, LanguageId = LanguageId.German, Id = 203, Label = "Information" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 3, LanguageId = LanguageId.French, Id = 301, Label = "Succès" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 3, LanguageId = LanguageId.Spanish, Id = 302, Label = "Éxito" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 3, LanguageId = LanguageId.German, Id = 303, Label = "Erfolg" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 4, LanguageId = LanguageId.French, Id = 401, Label = "Avertissement" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 4, LanguageId = LanguageId.Spanish, Id = 402, Label = "Advertencia" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 4, LanguageId = LanguageId.German, Id = 403, Label = "Erwärmen" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 5, LanguageId = LanguageId.French, Id = 501, Label = "Erreur" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 5, LanguageId = LanguageId.Spanish, Id = 502, Label = "Culpa" });modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = 5, LanguageId = LanguageId.German, Id = 503, Label = "Fehler" });
- it should contain :
-
Add the User Context initialization in service constructor. ex in role service:
/// <summary>/// Initializes a new instance of the <see cref="RoleAppService"/> class./// </summary>/// <param name="repository">The repository.</param>/// <param name="principal">The principal.</param>public RoleAppService(ITGenericRepository<Role> repository, IPrincipal principal, UserContext userContext): base(repository){this.principal = principal as BIAClaimsPrincipal;this.userContext = userContext;} -
Adapt the dto (if not optionDto)
- add translated fields:
/// <summary>/// Gets or sets the title./// </summary>public string Title { get; set; }/// <summary>/// Gets or sets the title translated./// </summary>public string TitleTranslated { get; set; }/// <summary>/// Gets or sets the description./// </summary>public string Description { get; set; }/// <summary>/// Gets or sets the description translated./// </summary>public string DescriptionTranslated { get; set; }
- add translated fields:
-
Adapt the mapper:
- ex for an optionDto in EntityToDto():
- without translation
Display = entity.Label,- with translation
Display = entity.RoleTranslations.Where(rt => rt.Language.Code == this.UserContext.Language).Select(rt => rt.Label).FirstOrDefault() ?? entity.Label, - for an entity display in CRUD
- translate for sort and filter in
ExpressionCollection<Notification> - for display in EntityToDto() add :
TitleTranslated = entity.NotificationTranslations.Where(rt => rt.Language.Code == this.UserContext.Language).Select(rt => rt.Title).FirstOrDefault() ?? entity.Title,DescriptionTranslated = entity.NotificationTranslations.Where(rt => rt.Language.Code == this.UserContext.Language).Select(rt => rt.Description).FirstOrDefault() ?? entity.Description,- for item only ( if (mapperMode == MapperMode.Item)) retrieve all translation (see example in NotificationMapper):
NotificationTranslations = entity.NotificationTranslations.Select(nt => new NotificationTranslationDto{DtoState = DtoState.Unchanged,Id = nt.Id,LanguageId = nt.LanguageId,Title = nt.Title,Description = nt.Description,}).ToList(), - ex for an optionDto in EntityToDto():