Content Localization
Warning
As of now, localization is only implemented in the backend. Authors can't yet record localized content in Entity-Apps.
Confinity supports localization of contents. Developers need to specify the supported languages in their application. Check the example and restrictions.
Configuration
With ConfinityOptions.EntityLocalization the developer can configure the available languages, opt-out properties or entities from localization.
Restrictions
- Combined primary- and foreign keys are not supported
- There is no way to set a translation to
NULLexplicitly, becauseNULLwill be replaced by the value of the default language.
Example
- Enable localization and define available languages.
private void InitializeEntityLocalization(ConfinityOptions confinityOptions)
{
var entityLocalization = confinityOptions.EntityLocalization;
var english = new CultureInfo("EN");
var german = new CultureInfo("DE");
var french = new CultureInfo("FR");
var hebrew = new CultureInfo("HE");
entityLocalization.DefaultLanguage = english;
// languages for all entities
entityLocalization.SetLocalizationLanguages(german, french);
var blogArticleConfiguration = entityLocalization.Configure<LocalizedBlogArticle>();
// languages for a specific entity
blogArticleConfiguration.SetLocalizationLanguages(german, french, hebrew);
// exclude some properties from localization
blogArticleConfiguration.DisableProperty(p => p.PublishDate);
blogArticleConfiguration.DisableProperty(p => p.Author);
// exclude an entity from localization
entityLocalization.Configure<LocalizedAuthor>().Disable();
}
- Define entities and ConfinityContents.
public class LocalizedBlogArticle : ConfinityEntity
{
public string Title { get; set; } = null!;
public ConfinityContent Content { get; set; } = null!;
public LocalizedCategory LocalizedCategory { get; set; } = null!;
public Guid LocalizedCategoryId { get; set; }
// to represent different images in different languages a ConfinityContent is used
public TranslatableImage TranslatableImage { get; set; } = null!;
public LocalizedAuthor? Author { get; set; }
public Guid? AuthorId { get; set; }
public DateTimeOffset PublishDate { get; set; }
}
[ConfinityPermissions(PermissionOption.None)]
public class LocalizedCategory : ConfinityEntity
{
public string Name { get; set; } = null!;
}
public class LocalizedAuthor : ConfinityEntity
{
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
}
public sealed class TextContent : ConfinityContent
{
public string Text { get; set; } = "";
public LinkModel? HomeLink { get; set; }
}
public sealed class TranslatableImage : ConfinityContent
{
public string Title { get; set; } = "";
public EntityRef<Asset>? Image { get; set; }
}
- Inject the scoped
ILocalizerto translate entities and ConfinityContents.
private void SeedData(IConfinityDbContext dbContext, ILocalizer localizer)
{
var textContent = new TextContent { Text = "A new report raises hope and prompts higher ambitions...", };
var demoArticle = new LocalizedBlogArticle
{
Title = "EU to exceed 2030 renewable target",
Content = textContent,
PublishDate = DateTimeOffset.Now
};
//ConfinityEntity
localizer.Translate(demoArticle, CultureInfo.GetCultureInfo("DE"))
.Set(p => p.Title, "EU will 2030-Ziel für erneuerbare Energien übertreffen");
//ConfinityContent
localizer.Translate(textContent, CultureInfo.GetCultureInfo("DE"))
.Set(p => p.Text, "Ein neuer Bericht gibt Anlass zur Hoffnung und zu höheren Ambitionen...");
dbContext.Set<LocalizedBlogArticle>().Add(demoArticle);
dbContext.SaveChangesAsync();
}
- In order to read localized data, set
CultureInfo.CurrentUiCultureto the desired language. Confinity will always use the default language as a fallback when no value is set.
public List<(LocalizedBlogArticle, Asset)> GetArticles(IConfinityDbContext dbContext, CultureInfo language)
{
CultureInfo.CurrentUICulture = language;
// returns articles with localized properties or a fallback to the default language
var blogArticles = dbContext.Set<LocalizedBlogArticle>()
.Include(p => p.LocalizedCategory)
.Include(p => p.Author)
.ToList();
// read images separately
var imageIds = blogArticles.Select(p => p.TranslatableImage.Image?.Id)
.OfType<Guid>()
.Distinct();
var images = dbContext.Set<Asset>()
.Where(p => imageIds.Contains(p.Id))
.ToList();
return blogArticles.Join(images,
article => article.TranslatableImage.Image?.Id,
image => image.Id,
(article, image) => (article, image))
.ToList();
}
Data persistence
Confinity Entity
Confinity analyzes all ConfinityEntities and their properties that are not ConfinityContents or Ids. For each of those properties and configured languages a shadow properties is created.
The shadow property:
- has the same name as the property with the suffix
_ISOwhere ISO is the two letter ISO language name - copies the following settings from the original property
- type
- collation
- unicode
- length
- precision
- value converter
- is always nullable
ConfinityContent
Each ConfinityContent internally holds a Dictionary with localized values per configured language.