Office365 – Multilingual content types, site columns and other site elements

As part of the new client side object model assemblies released in the SharePoint Online Client Components SDK, we now have additional capabilities for providing multilingual experiences in the Office365 SharePoint sites. This is good example of the cloud first release model, since this capability is not right now available for on-premises. It will be obviously included in future updates to the on-premises side as well, but there’s currently no public schedule for that.

What is supported

Here’s the list of labels which are currently supported labels which can be automatically translated when UI language is changed in the SharePoint site.

  • Site Title
  • Site Description
  • List Title
  • List Description
  • Content Type Name
  • Content Type Description
  • Site column Title
  • Site column Description

Notice that these are available only in the 16 versions of the client side object model assemblies. After installation of the SharePoint Online Client Components SDK, these will be available from the c:\program files\common files\Microsoft shared\web server extensions\16\ISAPI folder. These assemblies are having some dependencies on the capabilites which are currently only available from the Office365, so they might have some issues if you use them against on-premisese environment. If you only use capabilities which are in on-premises, you’ll be fine. This translation capability is currently only available in the Office365.

image

How do I enable multilingual sites in Office365?

So how do I actually get the translations to work in Offic365? There are few things which we need to configure. First thing is to enable multilingual support in the site level. This is done from Site Settings – Language Settings.

image

You will have to enable those languages which you want to be supported for this particular site.

image

Notice that currently we don’t have native way to enable language in site level using CSOM or out of the box remote pattern. This is known gap, which will be addressed sooner or later. If you have critical need to perform this, you can actually achieve it by using the controversial HTTP Post pattern, which has its own downsides.

After the previous steps, this site supports alternative languages, but to be able to see that in practice, you’ll have to select preferred language for your user profile. This can be done from OneDrive for business side or to be precise from your user profile editing pages. In following picture I’ve set Finnish as my preferred language, since Finglish was not available.

image

Now that we move back to the site where the Finnish has been enabled, we are able to see the translated content. Below pictures shows the front page in different languages.

image

image

Here’s how the list title is translated. Notice also that the title is translated properly in left navigation.

image

image

Here’s how the content type is visible when we edit document or list item properties. Notice also the site column translation.

image

image

Here’s how the site column is visible in the list with translated display text

image

image

How do I create and set the translations in code?

I’ve included here the full code for each of the examples on how the element is initially created and then how the multi-lingual support is added by using remove provisioning techniques. This would mean that we would take care of content type provisioning as part of the self service site collection creation for the collaboration sites. You can also use the same pattern against site collections which have been created from tenant admin tools, simply by running for example a console application against the just created site.

Site and list localization

Let’s start by creating the custom list which will be translated. This is pretty typical CSOM task, but added here for reference.

    1:  
    2: private static void CreateCustomList(ClientContext cc, Web web)
    3: {
    4:     ListCollection listCollection = cc.Web.Lists;
    5:     cc.Load(listCollection, lists => lists.Include(list => list.Title).
    6:                                     Where(list => list.Title == "LocalizeMe"));
    7:     cc.ExecuteQuery();
    8:     // Create the list, if it's not there...
    9:     if (listCollection.Count == 0)
   10:     {
   11:         ListCreationInformation newList = new ListCreationInformation();
   12:         newList.Title = "LocalizeMe";
   13:         newList.QuickLaunchOption = QuickLaunchOptions.On;
   14:         newList.TemplateType = (int)ListTemplateType.GenericList;
   15:         newList.Description = "LocalizeMe sample list";
   16:         List list = web.Lists.Add(newList);
   17:         cc.ExecuteQuery();
   18:     }
   19: }

Here’s the example method for site and list information localization. Notice that we simply access the UserResource objects of needed properties and we add the translation for specific language.

    1:  
    2: private static void LocalizeSiteAndList(ClientContext cc, Web web)
    3: {
    4:     // Localize site title
    5:     web.TitleResource.SetValueForUICulture("en-US", "Localize Me");
    6:     web.TitleResource.SetValueForUICulture("fi-FI", "Kielikäännä minut");
    7:     web.TitleResource.SetValueForUICulture("fr-FR", "Localize Me to French");
    8:     // Site description
    9:     web.DescriptionResource.SetValueForUICulture("en-US", 
   10:                             "Localize Me site sample");
   11:     web.DescriptionResource.SetValueForUICulture("fi-FI", 
   12:                             "Kielikäännetty saitti");
   13:     web.DescriptionResource.SetValueForUICulture("fr-FR", 
   14:                             "Localize to French in description");
   15:     web.Update();
   16:     cc.ExecuteQuery();
   17:  
   18:     // Localize custom list which was created previously
   19:     List list = cc.Web.Lists.GetByTitle("LocalizeMe");
   20:     cc.Load(list);
   21:     cc.ExecuteQuery();
   22:     list.TitleResource.SetValueForUICulture("en-US", "Localize Me");
   23:     list.TitleResource.SetValueForUICulture("fi-FI", "Kielikäännä minut");
   24:     list.TitleResource.SetValueForUICulture("fr-FR", "French text for title");
   25:     // Description
   26:     list.DescriptionResource.SetValueForUICulture("en-US", 
   27:                             "This is localization CSOM usage example list.");
   28:     list.DescriptionResource.SetValueForUICulture("fi-FI", 
   29:                 "Tämä esimerkki näyttää miten voit kielikääntää listoja.");
   30:     list.DescriptionResource.SetValueForUICulture("fr-FR", 
   31:                         "I have no idea how to translate this to French.");
   32:     list.Update();
   33:     cc.ExecuteQuery();
   34: }

Content type and site column localization

Let’s first create the site column remotely using CSOM. Again pretty typical remove operation task. Notice thought that you need to be careful on the used xml structure, since for example ID has to have also D capitalized, since otherwise a random Guid will be used as the identifier for the site column

    1:  
    2: private static void CreateSiteColumn(ClientContext cc, Web web)
    3: {
    4:     // Add site column to the content type if it's not there...
    5:     FieldCollection fields = web.Fields;
    6:     cc.Load(fields);
    7:     cc.ExecuteQuery();
    8:  
    9:     foreach (var item in fields)
   10:     {
   11:         if (item.InternalName == "ContosoString")
   12:             return;
   13:     }
   14:  
   15:     string FieldAsXML = @"<Field ID='{4F34B2ED-9CFF-4900-B091-4C0033F89944}' 
   16:                                     Name='ContosoString' 
   17:                                     DisplayName='Contoso String' 
   18:                                     Type='Text' 
   19:                                     Hidden='False' 
   20:                                     Group='Contoso Site Columns' 
   21:                                     Description='Contoso Text Field' />";
   22:     Field fld = fields.AddFieldAsXml(FieldAsXML, true, AddFieldOptions.DefaultValue);
   23:     cc.Load(fields);
   24:     cc.Load(fld);
   25:     cc.ExecuteQuery();
   26: }

Next we’ll need to create the content type. We also assign the specific ID for the site column remotely, which was one of the new things introduced lately as well.

    1:  
    2: private static void CreateContentTypeIfDoesNotExist(ClientContext cc, Web web)
    3: {
    4:     ContentTypeCollection contentTypes = web.ContentTypes;
    5:     cc.Load(contentTypes);
    6:     cc.ExecuteQuery();
    7:  
    8:     foreach (var item in contentTypes)
    9:     {
   10:         if (item.StringId == "0x0101009189AB5D3D2647B580F011DA2F356FB2")
   11:             return;
   12:     }
   13:  
   14:     // Create a Content Type Information object
   15:     ContentTypeCreationInformation newCt = new ContentTypeCreationInformation();
   16:     // Set the name for the content type
   17:     newCt.Name = "Contoso Document";
   18:     //Inherit from oob document - 0x0101 and assign 
   19:     newCt.Id = "0x0101009189AB5D3D2647B580F011DA2F356FB2";
   20:     // Set content type to be avaialble from specific group
   21:     newCt.Group = "Contoso Content Types";
   22:     // Create the content type
   23:     ContentType myContentType = contentTypes.Add(newCt);
   24:     cc.ExecuteQuery();
   25: }

And then we’ll bind the site column to the content type.

    1:  
    2: private static void AddSiteColumnToContentType(ClientContext cc, Web web)
    3:  {
    4:      ContentTypeCollection contentTypes = web.ContentTypes;
    5:      cc.Load(contentTypes);
    6:      cc.ExecuteQuery();
    7:      ContentType myContentType = contentTypes.GetById("0x0101009189AB5D3D2647B580F011DA2F356FB2");
    8:      cc.Load(myContentType);
    9:      cc.ExecuteQuery();
   10:  
   11:      FieldCollection fields = web.Fields;
   12:      Field fld = fields.GetByInternalNameOrTitle("ContosoString");
   13:      cc.Load(fields);
   14:      cc.Load(fld);
   15:      cc.ExecuteQuery();
   16:  
   17:      FieldLinkCollection refFields = myContentType.FieldLinks;
   18:      cc.Load(refFields);
   19:      cc.ExecuteQuery();
   20:  
   21:      foreach (var item in refFields)
   22:      {
   23:          if (item.Name == "ContosoString")
   24:              return;
   25:      }
   26:  
   27:      // ref does nt
   28:      FieldLinkCreationInformation link = new FieldLinkCreationInformation();
   29:      link.Field = fld;
   30:      myContentType.FieldLinks.Add(link);
   31:      myContentType.Update(true);
   32:      cc.ExecuteQuery();
   33:  }

Now the structures are created, we can then add the localization for the content type and the site column.

    1:  
    2: private static void LocalizeContentTypeAndField(ClientContext cc, Web web)
    3: {
    4:     ContentTypeCollection contentTypes = web.ContentTypes;
    5:     ContentType myContentType = contentTypes.GetById("0x0101009189AB5D3D2647B580F011DA2F356FB2");
    6:     cc.Load(contentTypes);
    7:     cc.Load(myContentType);
    8:     cc.ExecuteQuery();
    9:     // Title of the content type
   10:     myContentType.NameResource.SetValueForUICulture("en-US", 
   11:                                                     "Contoso Document");
   12:     myContentType.NameResource.SetValueForUICulture("fi-FI", 
   13:                                                     "Contoso Dokumentti");
   14:     myContentType.NameResource.SetValueForUICulture("fr-FR", 
   15:                                                     "Contoso Document (FR)");
   16:     // Description of the content type
   17:     myContentType.DescriptionResource.SetValueForUICulture("en-US", 
   18:                                     "This is the Contoso Document.");
   19:     myContentType.DescriptionResource.SetValueForUICulture("fi-FI", 
   20:                                     "Tämä on geneerinen Contoso dokumentti.");
   21:     myContentType.DescriptionResource.SetValueForUICulture("fr-FR", 
   22:                                     "French Contoso document.");
   23:     myContentType.Update(true);
   24:     cc.ExecuteQuery();
   25:  
   26:     // Do localization also for the site column
   27:     FieldCollection fields = web.Fields;
   28:     Field fld = fields.GetByInternalNameOrTitle("ContosoString");
   29:     fld.TitleResource.SetValueForUICulture("en-US", "Contoso String");
   30:     fld.TitleResource.SetValueForUICulture("fi-FI", "Contoso Teksti");
   31:     fld.TitleResource.SetValueForUICulture("fr-FR", "Contoso French String");
   32:     // Description entry
   33:     fld.DescriptionResource.SetValueForUICulture("en-US", 
   34:                             "Used to store Contoso specific metadata.");
   35:     fld.DescriptionResource.SetValueForUICulture("fi-FI", 
   36:                             "Tää on niiku Contoso metadatalle.");
   37:     fld.DescriptionResource.SetValueForUICulture("fr-FR", 
   38:                             "French Description Goes here");
   39:     fld.UpdateAndPushChanges(true);
   40:     cc.ExecuteQuery();
   41: }

Summary

MASSIVE! SUPER! AWESOME! MAGNIFICENT! GREAT! ÜBER! HIENOA! MÄKTIG!

It’s excellent to see the Office365 evolving constantly and we are seeing new capabilities introduced all the time. Not sure if the impact of these new capabilities are even truly understood, but this enables use to create portals remotely using just remote provisioning techniques, which is huge. These multilingual capabilities are definitely much needed capabilities for numerous enterprises. Personally I consider this as massive change for enabling our biggest customers to start moving to the cloud platform. This basically fulfils the initial requirements to be able to create content types and site columns using remote provisioning patterns and not to be forced to fall back on Sandbox solutions, which are hard to automate cross site collection creation.

Some reference links

Comments

  • Anonymous
    March 21, 2014
    The comment has been removed

  • Anonymous
    March 21, 2014
    Hi Ole, This is more about the new APIs on the CSOM side, so impact is on the code based side. App localization with resources is not direclty impacted.

  • Anonymous
    March 22, 2014
    Hi Vesa, Thanks for the clarification. It is a bit wierd, that considering we are encouraged to use apps, they still have not given us the oppertunity to ressource control a contenttype in an app ( and other problems considering localization ). While it may be possible to use the new APIs to connect to an installed app and mitigate the problem ( unsure if that is viable ) - it seems something is missing - especially considering the push of the app model. What we have here is a very nice update when working on the host web - where as far i can tell from all the buzz we're not even supposed to use lists and content types on the host ( we should use apps for that ) Am i completly missing the point here, or is the expected idea - to create lists fields and contenttypes (in an app)  using the app installed event - using the new apis? ( i presume that could work )  - dirty fix for lack of a working xml provisioning model in apps ? This may be outside of the scope of this - but considering you sit on alot of info - i thought it worth atleast trying to write this down as a question ;-)

  • Anonymous
    March 24, 2014
    Hi Ole, with the app web you can do resx based localization. This is quite nicely explained in the MSDN - msdn.microsoft.com/.../fp179919.aspx Like you mentioned however, this post was more concentrating on the remove provisioning techniques and the fact that now we can provide elements to the host web which are also translated based on the used language in host web.

  • Anonymous
    March 26, 2014
    Hey Vesa, Great update. Glad to see these features being released... we will definitely be taking advantage of these soon!

  • Anonymous
    March 26, 2014
    Hi Vesa, I know we "can" - fact though - it does not work ( but no mvp or otherwise seems willing to look at the problems - its alot of "lalalaa" ear holding going on - as i see it - maybe most people only use ENG) ;) Localized content types does not work in apps - when included in a schema.xml file ( because of the name / displayname required in schema file isnt being localized ) Localized custom fields does not change with language settings ( sharepoints own fields does ) What has me a bit confused though - is the fact that you guys have added this - but you dont seem to worry alot about the apps themselves - you seem to use them more as a "launch/ provisioning platform" and not as a content platform. Is the way ahead littered with using HOST web  and using apps as a provisioning platform ? or does apps get fixed ( localization wise ) along the way? - that was probably my main question / confusion point ;)

  • Anonymous
    March 26, 2014
    Hi Lytjohan, thanks for the feedback. This post was indeed more about the capabilities in the host web and the fact that we can now control the multi-lingual elements for content types and site columns when they are remotely provisioned. We are still 100% committed to bring proper multi-lingual support for the apps as well, no doubts. New capabilities and possibilities are now being introduced gradually cross the platform with the agile updates to the service. I'll be also personally looking into the app multi-lingual challenges in the app web now and will push on needed changes, including proper guidance.

  • Anonymous
    April 06, 2014
    The comment has been removed

  • Anonymous
    April 06, 2014
    The comment has been removed

  • Anonymous
    April 07, 2014
    Hi Nanddeep, I'm not really sure what's wrong in your case, since I tested the code you provided against my personal tenant and had no issues with 16 version CSOM and tenant APIs. Make sure thought that you use 16 version of the tenant APIs as well from www.microsoft.com/.../details.aspx

  • Anonymous
    April 07, 2014
    Hello Vesku, Adding the 16.0.0.0 version of Tenant API from below location, resolved the issue  C:Program FilesSharePoint Client Components16.0AssembliesMicrosoft.Online.SharePoint.Client.Tenant.dll Thank you very much :)

  • Anonymous
    April 19, 2014
    @Vesa, maybe a little bit offtopic, but I am trying to design a class library with helper method to do repetive tasks as creating content types, site columns, lists, etc, using the CAM.  However I want to know which is the best practice, passing ClientContext through the methods, web, or url and construct the clientcontext each time on each method. I found this, but I am not sure if the answer is OK, what do you think? sharepoint.stackexchange.com/.../sharepoint-2013-csom-is-it-better-to-pass-a-context-object-around-or-a-url

  • Anonymous
    April 22, 2014
    The comment has been removed

  • Anonymous
    April 23, 2014
    Hello Vesku, Is there any way to add MUI support on web, like it was with server side object model? web.AddSupportedUICulture(new CultureInfo(language.LCID));

  • Anonymous
    May 06, 2014
    Hi Nanddeep, That API is not unfortunately right now available from client side object model or using any remote methods. This is known gap, which will be addressed sooner or later. If you have critical need to perform this, you can actually achieve it by using the controversial HTTP Post pattern, which has its own downsides. This technique was explained in one of my previous blogs posts in here - blogs.msdn.com/.../ftc-to-cam-advance-http-remote-operations-for-sp2013.aspx.

  • Anonymous
    May 07, 2014
    Vesa, Thanks for a great post :-) Just to be sure: The ability to create multilingual site columns and content types in O365 is only available using CSOM? I've enabled multiple languages in O365, and can easily swap languages, can set multiple languages for MMS nodes - but have seen no way to set multiple descriptions and titles for CT's or columns using the UI...

  • Anonymous
    May 07, 2014
    The comment has been removed

  • Anonymous
    May 07, 2014
    Thanks for the explanation Vesa :-) I'll stick with the CSOM approach - will have to do it that way for the actual project anyway, the UI approach seems far to time-consuming even for a quick demo :-D

  • Anonymous
    May 08, 2014
    Thank you Vesa, HTTP Post Pattern is really a handy workaround to overcome limitations in CSOM APIs.

  • Anonymous
    June 17, 2014
    Hi Vesa, We have added few custom links in Site Actions from CSOM code with the help of UserCustomAction. We now want these links to be localized like other Site Actions links. So as per your opinion, which would be the best possible approach to achieve that? It doesn't look possible to add resources for custom actions from CSOM code. We have options likes: 1] Instead of adding Site Actions links from CSOM, Use declarative approach and use feature resources for localizing links. 2] Reading resources from JS file and apply to specific element in SiteActions menu at the time of page loading. or any other way ? Thanks for your inputs!

  • Anonymous
    February 23, 2015
    Hi Vesa, We have a content type where there are a bunch of choice columns. We used resource files for translations for the dropdown choices in an on-prem situation. Is there a way to add localization for the dropdown choice columns through the CSOM API? I haven't seen a way to do it so far? Thanks.

  • Anonymous
    March 02, 2015
    Hi Raj, this is not right now unfortunately supported for the choice values. You can do some level of translation with JS injection pattern, but that's not really that flexible. See following Office 365 Developer Patterns and Practices sample for reference (Scenario 2) - github.com/.../Core.JavaScriptCustomization

  • Anonymous
    March 31, 2015
    Hi Vesa, is it somehow possible to access and set these title/desc resources  through JSOM? I am creating a new list and would like to add these UI culture values: var listCreationInfo = new SP.ListCreationInformation(); listCreationInfo.set_title(title); listCreationInfo.set_templateType(listTemplate); //something like set_titleResource var oList = hostWeb.get_lists().add(listCreationInfo); Thx

  • Anonymous
    April 02, 2015
    Hi, I did some testing on my side and it's working pretty well when I create de site column first, and then apply it to the list. The problem I got is on site column already used in lists. Updating the alternate language is applied to the site column, but do not seems to be update to the list. This is really not usefull. any idea on the subject ? regards

  • Anonymous
    April 23, 2015
    Hi! Is there a way to do this on-prem at this point? Does March 2015 or April 2015 CU for 2013 add this support?

  • Anonymous
    April 26, 2015
    Hi Carl, This capability is unfortunately not in on-prem CSOM packages (April 2015 CU). Would provide feedback on the missing capability with officedev.uservoice.com

  • Anonymous
    June 15, 2015
    Hi Vesa, Great article, it helps me with site content localization. But I can not find how to localize navigation menu (structure or manage based navigation). Do you an information how to do it?

  • Anonymous
    October 15, 2015
    Hi Vesku, Is there any option to set multilingual for the list Views ?

  • Anonymous
    November 10, 2015
    Hi Ajesh, There's no TitleResource property for Views, so this is not available as such for the view titles. Columns in views are translated based on the configuration at the site column level.

  • Anonymous
    December 18, 2015
    Hi Vesa, Based on this post I've tried and it worked for the alternate language but it does not change value for the default language. Any idea?

  • Anonymous
    July 05, 2016
    As the content type is getting localized, are functioning of the data services in our app is getting hampered. So, I request you kindly give us a way to get the english version of the content type in c# (for example: for a document).