Today I chatted about multilingual SharePoint sites with a fellow consultant, Syed Hanifa. We were both wondering if it’s possible to deploy translations using Power Automate without premium connectors and without using PnP PowerShell, Azure Functions and Azure Automation.
There seems to be no official way to deploy translations to SharePoint Online sites via the REST API or the Graph API.
However, I do know that we can do it with the help the Client Object Model (CSOM) and the PnP PowerShell module.
There is a SetValueForUICulture
method that allows you to set the translation value for a specific language. It is available for many SharePoint objects such as Content type, Field, Site, Web and Navigation Node.
# Set a title$SOME_SHAREPOINT_OBJECT.TitleResource.SetValueForUICulture('fr-FR','Title in French')# Set a description$SOME_SHAREPOINT_OBJECT.DescriptionResource.SetValueForUICulture('fr-FR','Description in French')
Consider the following examples.
Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/MultilingualTeamSite -ClientId ca5e9978-cf4c-4bc4-981c-67e34b55d810 -ClientSecret '...' -WarningAction Ignore$web = Get-pnpWeb$web.TitleResource.SetValueForUICulture('fr-FR','Site Title French')$web.DescriptionResource.SetValueForUICulture('fr-FR','French Site Description ')$web.Update()Invoke-PnPQuery
$list = Get-PnPList MyList$list.TitleResource.SetValueForUICulture('fr-FR','Multilingual List French')$list.DescriptionResource.SetValueForUICulture('fr-FR','French Description')$list.Update()Invoke-PnPQuery
$nodes = Get-PnPNavigationNode -Location QuickLaunch$node = $nodes | Where-Object {$_.Title -eq "Documents"}$node.TitleResource.SetValueForUICulture('fr-FR','French Documents translation')$node.Update()Invoke-PnPQuery
How do I know what translations are already set? Should I open the SharePoint site using different browser profile with different language settings? No, you don’t have to. Simply use the GetResourceEntries()
method. The GetResourceEntries()
method will get you translations for all sorts of SharePoint objects:
# Check existing translations for a list$ResourceEntriesForTitle = $list.TitleResource.GetResourceEntries()$ResourceEntriesForDescription = $list.DescriptionResource.GetResourceEntries()Invoke-PnPQuery$ResourceEntriesForTitle$ResourceEntriesForDescription# Check existing translations for a field$ResourceEntriesForTitle = $field.TitleResource.GetResourceEntries()$ResourceEntriesForDescription = $field.DescriptionResource.GetResourceEntries()Invoke-PnPQuery$ResourceEntriesForTitle$ResourceEntriesForDescription
OK, these examples are great, but how does PnP PowerShell do it? Since I wanted to see what’s going on under the hood, I used Fiddler to inspect the requests made by PnP PowerShell.
$web.TitleResource.SetValueForUICulture('fr-FR','Site Title French')
results in the following POST request:
POST /_vti_bin/client.svc/ProcessQueryBODY:<Request AddExpandoFieldTypeSuffix="true"SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0"ApplicationName="SharePoint PnP PowerShell Library"xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="8" ObjectPathId="7" /><Method Name="SetValueForUICulture" Id="9" ObjectPathId="7"><Parameters><Parameter Type="String">fr-FR</Parameter><Parameter Type="String">Site Title French</Parameter></Parameters></Method><Method Name="Update" Id="10" ObjectPathId="3" /></Actions><ObjectPaths><Property Id="7" ParentId="3" Name="TitleResource" /><Identity Id="3"Name="67df09a1-0018-4000-e1c1-8ca67d1a058f|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e" /></ObjectPaths></Request>
The ProcessQuery
endpoint is used by the CSOM to send a batch of requests to SharePoint. The Actions
element contains the requests that we want to send. The ObjectPath
element is used to identify the object that we are working with.
The SetValueForUICulture
method in the XML request is the action we are looking for. It sets the translation value of for a specific language, for a specific object
<Method Name="SetValueForUICulture" Id="9" ObjectPathId="7"><Parameters><Parameter Type="String">fr-FR</Parameter><Parameter Type="String">Site Title French</Parameter></Parameters></Method>
The Identity
element in the XML request is used to identify the object that we are translating. It is a series of GUIDs that represent the object in SharePoint.
<Identity Id="3"Name="67df09a1-0018-4000-e1c1-8ca67d1a058f|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e" />
Let’s break it down:
67df09a1-0018-4000-e1c1-8ca67d1a058f
- a Web Request ID. This can be a random GUID and won’t change the result of the request.740c6a0b-85e2-48a0-a494-e0f1759d4aa7
- is a constant value that stands for the Microsoft.SharePoint.SPObjectFactory class (Microsoft.SharePoint assembly). It is used to create objects in SharePoint. All you need to know is that it is a constant value and it won’t change.site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3
- is the GUID of the site collection.web:7cfedc29-bb5b-4500-a774-e11edb25f54e
- is the GUID of the web (subsite).If you want to translate a list, you will also need to set the ‘list:GUID’ part of the Identity element. Same goes for the fields, navigation nodes and other SharePoint objects. It all depends on the object you are translating. The best way to get the Identity is to use the PnP PowerShell and Fiddler to capture the requests.
Using this knowledge, we can now create our own REST API requests to set translations for SharePoint objects. Let’s send a request using ‘Power Automate’s Send an HTTP request to SharePoint’ action:
I was delighted to see that the translation was successfully added to the site title.
Then using Fiddler, I captured the requests for other translation operations and created a Power Automate flow that sets translations for the site title, site description, list title, list description and quick navigation nodes.
First, I wanted to get important GUIDs:
GET /_api/site/idaccept: application/json; odata=nometadata
GET /_api/web/idaccept: application/json; odata=nometadata
GET _api/web/lists/getByTitle('MyList')/idaccept: application/json; odata=nometadata
I am using the application/json;odata=nometadata for the Accept header. This is because I don’t need the metadata in the response.
Then, using the Compose action, I prepared a full object path for the SharePoint translation:
You might notice that the Field ID is hardcoded. This is because this ID was already known to me. But you can get it using a separate REST call.
Finally, to set the translation, I used the ‘Send an HTTP request to SharePoint’ action:
Similarly, I added a translation for the list title:
And for the site:
Translating the quick navigation node was a bit more complicated. I had to get the navigation node ID first:
_ObjectIdentity_
property contains the full path to the navigation node. I used it to set the translation for the quick navigation node.
"_Child_Items_": [{"_ObjectType_": "SP.NavigationNode","_ObjectIdentity_": "09bf09a1-f062-4000-c72f-d287bb8974ca|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e:navnode:1031","Title": "Sample Page","Url": "/sites/MultilingualTeamSite"},{"_ObjectType_": "SP.NavigationNode","_ObjectIdentity_": "09bf09a1-f062-4000-c72f-d287bb8974ca|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e:navnode:2002","Title": "Notebook","Url": "/sites/MultilingualTeamSite/_layouts/15/Doc.aspx?sourcedoc={382b7dc2-4009-4025-8aeb-5a7bec436c84}&action=editnew"},{"_ObjectType_": "SP.NavigationNode","_ObjectIdentity_": "09bf09a1-f062-4000-c72f-d287bb8974ca|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e:navnode:2001","Title": "Documents","Url": "/sites/MultilingualTeamSite/Shared Documents/Forms/AllItems.aspx"},{"_ObjectType_": "SP.NavigationNode","_ObjectIdentity_": "09bf09a1-f062-4000-c72f-d287bb8974ca|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e:navnode:2003","Title": "Pages","Url": "/sites/MultilingualTeamSite/SitePages/Forms/ByAuthor.aspx"}]
Then I used the quick navigation link’s _ObjectIdentity_
to set the translation:
09bf09a1-f062-4000-c72f-d287bb8974ca|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:5900d0c7-5c7c-47c0-97f3-ca5ac9c392a3:web:7cfedc29-bb5b-4500-a774-e11edb25f54e:navnode:2001
Setting the translation for the quick navigation node:
I decided to share the flow with you. You can download it and import it into your environment. You can use it as a starting point for your own multilingual SharePoint sites.
👉 Click here to download a sample Flow 👈
I was able to set translations for SharePoint objects using Power Automate and the REST API. This is a great alternative to using PnP PowerShell, Azure Functions and Azure Automation. Although it requires some knowledge of the SharePoint REST API, Fiddler and XML, it is a great way to automate translations without using premium connectors.