ANSI C Based OPC UA Client/Server/PubSub SDK  1.9.3.467
Localization of strings

Overview

OPC UA provides localization of text provided by an OPC UA server.

The DataType that is used for this feature is the LocalizedText structure that consist of a LocaleId and a localized text string.

The LocalizedText is provided for Node Attributes like DisplayName and Description, for Variable Values with DataType LocalizedText or for Event fields like the Event Message. See Base Node Class for more details.

The OPC UA client selects the language(s) of interest by specifying a LocaleId array for the Session created with a server.

The server provides the supported LocaleIds in the Variable Server > ServerCapabilities > LocaleIdArray. The server may support more languages but it would be good to expose at least the default language.

The server returns one of the requested languages if supported. If none of the client requested language is supported the server returns the default language.

The language provided to the client is always part of the LocalizedText DataType. Therefore the client knows the returned language even if the server does not support one of the requested languages.

The default implementations provided with the SDK are only able to manage one language for places where LocalizedText is handled.

If a server implementation want to manage more than one (default) language, the server implementation need to check Read and Browse results and provide a translation if required based on the Session settings.

The following sample code shows such handlers added to corresponding SDK demo server functions. The extensions are marked with 'Code added for LocalizedText handling'.

Localization of Browse results

The following code shows the extended handling for the Browse Service. The helper function checkTranslation() is described below in Helper function.

IFMETHODIMP(UaProvider_Demo_BrowseAsync)(UaServer_ProviderBrowseContext* a_pBrowseCtx)
{
OpcUa_Int32 i;
OpcUa_BaseNode *pNode;
OpcUa_NodeId *pNodeId = OpcUa_Null;
OpcUa_BrowseDescription *pNodeToBrowse = OpcUa_Null;
UaServer_AddressSpace *pAddressSpace = &(g_pDemoProvider->AddressSpace);
OpcUa_ReturnErrorIfArgumentNull(a_pBrowseCtx);
pReq = a_pBrowseCtx->pRequest;
pRes = a_pBrowseCtx->pResponse;
/* we will send exactly one callback */
UaBase_Atomic_Increment(&a_pBrowseCtx->nOutstandingCbs);
for (i = 0; i < pReq->NoOfNodesToBrowse; i++)
{
pNodeToBrowse = &pReq->NodesToBrowse[i];
pNodeId = &pNodeToBrowse->NodeId;
if (!UaServer_IsNamespaceOwner(g_pDemoProvider, pNodeId->NamespaceIndex))
continue;
UaServer_GetNode(pAddressSpace, pNodeId, &pNode);
if (pNode != OpcUa_Null)
{
UaServer_BrowseInternal(pNode, a_pBrowseCtx, i);
/********** Code added for LocalizedText handling *******************************/
/* Check if we need to translate LocalizedText */
if ((pReq->NoOfNodesToBrowse == a_pBrowseCtx->pResponse->NoOfResults) &&
(pRes->Results[i].NoOfReferences > 0))
{
OpcUa_Int32 iRefs;
for (iRefs = 0; iRefs < pRes->Results[i].NoOfReferences; iRefs++)
{
checkTranslation(a_pBrowseCtx->pSession, &pRes->Results[i].References[iRefs].DisplayName);
}
}
/********** Code added for LocalizedText handling *******************************/
}

Localization of Attribute Read

The following code shows the extended handling for the Read Service. The helper function checkTranslation() is described below in Helper function.

IFMETHODIMP(UaProvider_Demo_ReadAsync)(UaServer_ProviderReadContext* a_pReadCtx)
{
int i;
OpcUa_BaseNode *pNode;
OpcUa_NodeId *pNodeId = OpcUa_Null;
OpcUa_ReadValueId *pNodeToRead;
UaServer_AddressSpace *pAddressSpace = &(g_pDemoProvider->AddressSpace);
OpcUa_ReturnErrorIfArgumentNull(a_pReadCtx);
pReq = a_pReadCtx->pRequest;
pRes = a_pReadCtx->pResponse;
/* we will send exactly one callback */
UaBase_Atomic_Increment(&a_pReadCtx->nOutstandingCbs);
for (i = 0; i < pReq->NoOfNodesToRead; i++)
{
pNodeToRead = &pReq->NodesToRead[i];
pNodeId = &pNodeToRead->NodeId;
if (!UaServer_IsNamespaceOwner(g_pDemoProvider, pNodeId->NamespaceIndex))
continue;
UaServer_GetNode(pAddressSpace, pNodeId, &pNode);
if (pNode)
{
UaProvider_Demo_UserData *pUserData;
/* check if value is readable */
if (pNodeToRead->AttributeId == OpcUa_Attributes_Value
&& OpcUa_BaseNode_GetType(pNode) == eVariable
&& (OpcUa_Variable_GetAccessLevel(pNode) & OpcUa_AccessLevels_CurrentRead) == 0)
{
pRes->Results[i].StatusCode = OpcUa_BadNotReadable;
continue;
}
UaServer_ReadInternal(pNode, a_pReadCtx, i);
/* custom handling for nodes with UserData */
pUserData = OpcUa_BaseNode_GetUserData(pNode);
if (pUserData != OpcUa_Null &&
pUserData->UserDataType != UaProvider_Demo_UserDataType_Base &&
pNodeToRead->AttributeId == OpcUa_Attributes_Value &&
OpcUa_IsGood(pRes->Results[i].StatusCode) &&
OpcUa_BaseNode_GetType(pNode) == eVariable)
{
UaProvider_Demo_UserData_GetValue(pUserData, (OpcUa_Variable*)pNode, &pRes->Results[i]);
}
/********** Code added for LocalizedText handling *******************************/
/* Check if we need to translate LocalizedText */
if (pRes->Results[i].Value.Datatype == OpcUaType_LocalizedText)
{
if (pRes->Results[i].Value.ArrayType == OpcUa_VariantArrayType_Scalar)
{
if (pRes->Results[i].Value.Value.LocalizedText)
{
checkTranslation(a_pReadCtx->pSession, pRes->Results[i].Value.Value.LocalizedText);
}
}
else if (pRes->Results[i].Value.ArrayType == OpcUa_VariantArrayType_Array)
{
OpcUa_Int32 ltIndex;
if (pRes->Results[i].Value.Value.Array.Length > 0)
{
for (ltIndex = 0; ltIndex < pRes->Results[i].Value.Value.Array.Length; ltIndex++)
{
checkTranslation(a_pReadCtx->pSession, &pRes->Results[i].Value.Value.Array.Value.LocalizedTextArray[ltIndex]);
}
}
}
}
/********** Code added for LocalizedText handling *******************************/
}

Localization of Event fields

The translation of Event fields can be done through implementation of the callback function UaServer_pfServer_TranslateEventField. The callback can be registered with UaServer_SetCallback_TranslateEventField().

Helper function

The helper function used in the sample code above. This is only incomplete pseudo code. The functionality to get the translated string is application specific.

void checkTranslation(UaServer_PublicSession* a_pSession, OpcUa_LocalizedText* a_pltText)
{
OpcUa_String newString;
OpcUa_Boolean requiresUpdate = OpcUa_False;
if (a_pSession->NoOfLocaleIDs > 0)
{
if (OpcUa_String_Compare(&a_pSession->LocaleIDs[0], &a_pltText->Locale))
{
OpcUa_String_Initialize(&newString);
/* The LocalId does not match the first requested */
/* TBD - application specific code */
/* Check if any of the requested LocaleIds in a_pSession->LocaleIDs is supported */
/* Get translation for the text */
/* TBD - application specific code */
/* If translation is available, update the text for the first supported LocaleId in a_pSession->LocaleIDs */
if (requiresUpdate != OpcUa_False)
{
OpcUa_String_Clear(&a_pltText->Text);
OpcUa_String_CopyTo(&newString, &newString);
}
}
}
}