Banner

Multilingual Publishing in Sitefinity

Jun 20, 2017

Initially Publish your Content in a Non-Default Language

Sitefinity has great multilingual publishing capabilities. From the perspective of an end-user / content manager, the backend offers up a myriad of methods to create content items both in the site’s default language as well as any other languages activated/enabled in the site itself. They’re all usually just one click away, and provide a good experience.

But what about the development side of things? Say we have a custom widget that creates a content item, but the language either must be something apart from the default, or is user-selectable before creation occurs? How do we instruct Sitefinity via the API to publish our content item in another language? Let’s find out by trying to create a new News item (but what we do here will also work for dynamic content).

Collecting the Data for Creation

Let’s say we had a custom widget that gives us some basic fields for news, including some custom fields, as well as specifying what language we want to Publish in. This widget gives us an object of type “CreateNewsViewModel” that has these fields populated for us. The CreateNewsViewModel looks something like this:

public class CreateNewsViewModel
{
    // Built-in News fields
    public string Title { get; set; }
    public string Summary { get; set; }
    public string Content { get; set; }
     
    // Custom fields on News
    public string CustomStringField { get; set; }
    public bool CustomBoolField { get; set; }
     
    // Selected language
    public string SelectedCultureName { get; set; }
  
    // Classification field
    public Guid SelectedTagId { get; set; }
}

In our case we set some built-in fields, a couple custom fields, a Tag (passing along its ID), and what Culture (i.e. Language) we wish to store.

At this point you might be asking how we get SelectedCultureName. Easy! Sitefinity’s API exposes the currently-available languages to the frontend, and we can use that list to create a dropdown selector in our widget, then passing along the selected culture’s name in our above model. The code to fetch the available cultures is:

CultureInfo[] availableCultures = SystemManager.CurrentContext.AppSettings.DefinedFrontendLanguages;

In our method (coming up in the next bit) we’ll take the name, and get our Culture object back (because it’s easier to pass strings around to/from an MVC model, and to verify that we didn’t get junk data / invalid culture info).

Creating the Item

Now we get to the fun part. The first step in multilingual publishing is to simply do what you always do when creating a news item by invoking the NewsManager:

private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    NewsManager newsManager = NewsManager.GetManager();
    NewsItem item = newsManager.CreateNewsItem();
     
    DateTime creationDate = DateTime.UtcNow;
    CultureInfo culture = SystemManager.CurrentContext.AppSettings.DefinedFrontendLanguages.Single(c => c.Name == viewModel.SelectedCultureName);
     
    // Not done yet!
}

Here we not only create our “item” variable using the manager, but also get the current DateTime (in UTC) and get our CultureInfo object based on the one selected in our viewmodel. Now we’ll start setting our field values to what we gave it, using our culture to make sure that the correct translation of the field gets the value instead of the site’s default language:
private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    // ... Earlier code snipped ...
     
    item.Title.SetString(culture, viewModel.Title);
    item.Summary.SetString(culture, viewModel.Summary);
    item.SetString("Content", viewModel.Content, culture);
     
    // Not done yet!
}

You see for our built-in short text fields Title and Summary that we’re simply calling “SetString()” on them, passing the culture first and the value we wish to store second. In multilingual publishing the culture object is key! Note that for long text fields (Content in this case) the syntax is different. Wait what?

Pitfalls of Exposed LString Methods

But what about long text fields like Content? The code lets us call “item.Content.SetString(culture, viewModel.Content)”, so why the inconsistency? In fact, there’s also an array indexer built into Sitefinity LStrings that would let us call “item.Content[culture] = viewModel.Content” instead of the more cumbersome SetString method!

As it turns out, while Sitefinity and its LString class has those capabilities exposed for use in its API, they do not function correctly. They either still store the data in the default language field, or simply do not record the value at all! You have to call “SetString” on the data item itself (in this case, the “item” variable we created), passing the field name, string to store, and then the culture, otherwise it will not work.

If you like consistency, you can also use the exact same syntax for the short text fields, so that all field setting (short text or long text) is consistent in your code. While I do like consistency, I also like using the strongly-typed fields where possible (using item.Title instead of item.SetString(“Title”)) because the value of the strings aren’t validated (i.e. throw an exception) until runtime. It’s entirely up to you though!

With that in mind, let’s continue with multilingual publishing.

Use Long Text SetString for Custom Fields

This one’s easy enough. Since there is no strongly-typed property we can set for custom fields, we simply use the same method for custom fields that we do for long text fields:

private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    // ... Earlier code snipped ...
     
    item.SetString("CustomStringField", viewModel.CustomStringField, culture);
     
    // Not done yet!
}

Set Non-Translatable Fields Normally

For fields such as classifications, non-strings (i.e. booleans), etc., set them like you would any normal content item. They don’t have translatable fields so there’s nothing special to do here when doing multilingual publishing. Here we set our custom boolean field and our Tags field, as well as the boilerplate date content fields like we would normally:

private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    // ... Earlier code snipped ...
     
    item.SetValue("CustomBoolField", viewModel.CustomBoolField);
     
    // Syntax for adding a Tag
    item.Organizer.AddTaxa("Tags", viewModel.SelectedTagId);
     
    item.DateCreated = creationDate;
    item.LastModified = creationDate;
    item.PublicationDate = creationDate;
     
    // Not done yet!
}

UrlName Requires Culture!

Since each version of the news item will have its own URL Name, we need to set it like we would any other translatable string:

item.UrlName.SetString(culture, viewModel.Title.SafeUrlName()); // SafeUrlName() is a string extension method I have to remove unsafe-for-url characters from a string.

Publishing the Item

We’ve reached the end of our method! All we have to do now is invoke the usual publishing methods Sitefinity exposes, only passing along the Culture where necessary. Once we do that, our multilingual publishing process is complete!

private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    // ... Earlier code snipped ...
     
    newsManager.SaveChanges();
    newsManager.RecompileAndValidateUrls(item);
    item.ApprovalWorkflowState.SetString(culture, "Published");
    newsManager.Lifecycle.Publish(item, culture);
    item.SetWorkflowStatus(newsManager.Provider.ApplicationName, "Published", culture);
    newsManager.SaveChanges();
}

In this case you see we’re calling the “SetString” method on ApprovalWorkflowState, and are passing along the culture to the Publish and SetWorkflowStatus methods as well. Once we save those changes, our news item is created using the culture we specified. We’ve done it!

Complete Creation Method

For convenience here’s the entire method we pieced together above:

private void CreateNewsItem(CreateNewsViewModel viewModel)
{
    NewsManager newsManager = NewsManager.GetManager();
    NewsItem item = newsManager.CreateNewsItem();
     
    DateTime creationDate = DateTime.UtcNow;
    CultureInfo culture = SystemManager.CurrentContext.AppSettings.DefinedFrontendLanguages.Single(c => c.Name == viewModel.SelectedCultureName);
     
    item.Title.SetString(culture, viewModel.Title);
    item.Summary.SetString(culture, viewModel.Summary);
    item.SetString("Content", viewModel.Content, culture);
     
    item.SetString("CustomStringField", viewModel.CustomStringField, culture);
    item.SetValue("CustomBoolField", viewModel.CustomBoolField);
     
    // Syntax for adding a Tag
    item.Organizer.AddTaxa("Tags", viewModel.SelectedTagId);
     
    item.DateCreated = creationDate;
    item.LastModified = creationDate;
    item.PublicationDate = creationDate;
     
    newsManager.SaveChanges();
    newsManager.RecompileAndValidateUrls(item);
    item.ApprovalWorkflowState.SetString(culture, "Published");
    newsManager.Lifecycle.Publish(item, culture);
    item.SetWorkflowStatus(newsManager.Provider.ApplicationName, "Published", culture);
    newsManager.SaveChanges();
}

Wrap Up

Multilingual publishing using the Sitefinity API is easy. There are some pitfalls to avoid and there’s a little bit of syntax involved, but it largely revolves around curating the appropriate Culture object, and passing that in whenever you need to set a translatable field. Not all fields are translatable, but for those the Culture object is ignored entirely and you just do what you normally do.

The above will work with custom content types as well. Instead of NewsManager, you use DynamicModuleManager. And since everything is a custom field, you simply use the item.SetString(“MyField”, stringValue, culture) syntax we used for some of the fields for news items.

And that’s that. You now have the power of multilingual publishing at your fingertips.

Load more comments
comment-avatar

Copyright © 2017 Alain "Lino" Tadros