Confinity Documentation
  • Latest Version
  • Latest Version
  • Getting Started

    • Introduction
    • Core Concepts
    • Create an Application
    • Glossary
  • Essentials

    • Authentication & SSO
    • Breaking Changes
    • Roslyn Source Analyzers
    • Changelog
    • ConfinityContent
    • ConfinitySelectable
    • Confinity Schedules
    • Data Seeding
    • Development guidelines [WIP]
    • Entity App
    • Entity Form
    • Entity Permissions
    • Frontend Configuration
    • Images
    • Known Issues
    • Localization
    • Migrations
    • Modules [WIP]
    • On-Site Editing
    • Settings
    • Cascade Delete
    • Replication
    • Infrastructure
  • Modules

    • Analytics Module
    • Assets Module
    • Blog Module
    • Cookie Consent Module
    • Forms Module
    • Friendly Captcha (Forms Module )
    • GeoIP Module
    • Htmx
    • Mail Module
    • Mailing Module
    • MediaPlayer Module
    • GoogleMyBusiness Module
    • OpenTelemetry Module
    • Pages Module [WIP]
    • Pattern Library Module
    • SIX Saferpay (worldline) Module
    • Products Module
    • Search Module
    • Wizard Module
  • Guides

    • Create a Custom Entity App Form Element
    • Date and Time
    • Entity Change Listener
    • File Upload / Temp File
    • HTTP security headers
    • conventions [WIP]
    • How to use Confinity with nginx
    • Notifications
    • Nullability
    • Rename Entity
    • Schedules
    • Useful snippets
    • Content Localization
  • Design Guidelines

    • Introduction
    • Page Components
    • Forms Module

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 NULL explicitly, because NULL will be replaced by the value of the default language.

Example

  1. 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();
}

  1. 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; }
}

  1. Inject the scoped ILocalizer to 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();
}

  1. In order to read localized data, set CultureInfo.CurrentUiCulture to 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 _ISO where 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.

Prev
Useful snippets