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 (30)

    1. Ole Lytjohan says:

      Finally, thanks for the update :o)

      Does that mean – that it will also work on xml provisioning files inside apps and so forth using resource files ( as we're used to ) – or is the new localization updates for client components sdk only?

      Regards

      Ole

    2. sonofthesun says:

      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.

    3. Ole Lytjohan says:

      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 😉

    4. sonofthesun says:

      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.

    5. CoryPeters says:

      Hey Vesa,

      Great update. Glad to see these features being released… we will definitely be taking advantage of these soon!

    6. Lytjohan says:

      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 😉

    7. sonofthesun says:

      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.

    8. Nanddeep Nachan says:

      Hello Vesku,

      I did install the

      1. 16 versions of the client side object model assemblies.

      2. SharePoint Online Client Components SDK (http://www.microsoft.com/…/details.aspx)

      Now, when I am trying to load Tenant like below:

      Microsoft.Online.SharePoint.TenantAdministration.Tenant tenant = new Tenant(ctx);

      ctx.Load(tenant, t => t.ResourceQuota, t => t.ResourceQuotaAllocated, t => t.StorageQuota, t => t.StorageQuotaAllocated);

      ctx.ExecuteQuery();

      It gives error:

      Method Microsoft.SharePoint.Client.ClientRuntimeContext.Load: type argument 'Microsoft.Online.SharePoint.TenantAdministration.Tenant' violates the constraint of type parameter 'T'.

      so, is that 16 version of client side assemblies does not work with Tenant object? Or am I missing something?

      Could you please guide?

    9. Nanddeep Nachan says:

      Hello Vesku,

      I wrongly mentioned the url for

      SharePoint Online Client Components SDK (http://www.microsoft.com/…/details.aspx)

    10. sonofthesun says:

      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 http://www.microsoft.com/…/details.aspx

    11. Nanddeep Nachan says:

      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 🙂

    12. Luis says:

      @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

    13. sonofthesun says:

      Hi Luis,

      we are just about to get new refactored Office AMS version released with centralized component for generic remote operations. Method signatures are only with the Web object, since you can get the client context from it directly. This keeps things pretty simple. Obviously if there's need for site collection level modifications, signature looks pretty different.

    14. Nanddeep Nachan says:

      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));

    15. sonofthesun says:

      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.

    16. Roger Langedal says:

      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…

    17. sonofthesun says:

      Hi Roger,

      yes and no… you can easily do this from CSOM using the model explained in here. You can actually do this also from UI, but the process is pretty difficult. If you have multiple languages enabled and you set the site language to for example Finnish before updating hte site column title, that update is done to Finnish translation of the site column title. This means that if you then move back to English, you'll see the original entry.

      There's to UI where you could see all the translations for different languages in single view.

    18. Roger Langedal says:

      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 😀

    19. Nanddeep Nachan says:

      Thank you Vesa,

      HTTP Post Pattern is really a handy workaround to overcome limitations in CSOM APIs.

    20. Shriram says:

      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!

    21. Raj Sivakumar says:

      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.

    22. sonofthesun says:

      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

    23. Thomas Oplatek says:

      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

    24. Merlin says:

      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

    25. Carl-Johan Tireus says:

      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?

    26. sonofthesun says:

      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

    27. Evgeny Goncharov says:

      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?

    28. Ajesh Kakkoprath says:

      Hi Vesku,

      Is there any option to set multilingual for the list Views ?

    29. sonofthesun says:

      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.

    30. Sam says:

      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?