Translating project

Thanks @JordanCClark for your work on this. I have the same issues that @zxcslo has with HMI users in other countries. Excel is the lowest common denominator, and we are looking into getting translations from Ignition into Excel-based CSV and then back into Ignition. In addition, we leave the site where SCADA is located (often without Internet connection) and the customer needs to be able to translate something customer specific. So there must be an easy method to have customers translate things. I am hoping I don’t have to resort to our old method of storing our translations in SQL, and making SQL calls to change the text on the screens. Any thoughts?

translationManager.proj (11.8 KB)

This is why I like Ignition, it’s so open that you can plug in your own code to achieve this.

Here, I made a page (as template, so it can be reused over projects) that loads all terms and translations to a certain language into a table. The table can be edited directly, or exported and imported (to CSV in this case, but Excel would also work).

Disclaimer: I’m not responsible for any loss of data (translation strings or other), please make a project backup before using it.

4 Likes

I had been working this up on and off (mostly off) for a few months. This works with XML exports of the Translation Manager, and will handle multiple locales at once. You will need a Microsoft Azure account, but the free version is, as of this writing, 2 million characters per month.

  1. Export XMLs from Translation Manager

  2. Run the script in whatever fashion you choose.

  3. Resultant files will be in the same directory.

  4. Import the translated files back into the Translation Manager.
    NOTE: Currently, the locales are not being correncly parsed when importing back in. (see this post). But, manually selecting the locale dropdown will let you work around it. :slight_smile:

import javax.xml.parsers
import javax.xml.transform
import java.io.FileOutputStream as FOS
import httplib, urllib, uuid, codecs, os, glob

# **********************************************
# *** Update or verify the following values. ***
# **********************************************

# Replace the subscriptionKey string value with your valid subscription key.
subscriptionKey = 'put_your_own_key_here'

host = 'api.cognitive.microsofttranslator.com'
path = '/translate?api-version=3.0'

#*************************************************

filepath = system.file.openFile()
# Flag to tell it we are processing all of the files are just the selected one.
allFiles = True

if filepath != None:
  # Base file name without locale
  baseName = filepath[:-7]
  # Extension of the file
  extension = filepath[-4:]
  if allFiles == True:
    fileFilter = baseName + '*' + extension
  else:
    fileFilter = filepath

  # create file list 
  fileList = glob.glob(fileFilter)

  # create list of locales
  langList =  [f.replace(baseName,'').replace(extension, '')[1:] for f in fileList]

  # create language parameters to use with Microsoft Translate
  params = '&to='+'&to='.join(langList)
  
  # open the selected file and extract keys to be translated
  dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance()
  db = dbf.newDocumentBuilder()
  dom = db.parse(filepath)
  doc = dom.getDocumentElement()
  entries = doc.getElementsByTagName('entry')
  keyList =[]
  for i in range(entries.getLength()):
    node = entries.item(i)
    keyList.append(node.getAttribute('key'))
	
  # header for Microsoft Translate API call
  headers = {
        'Ocp-Apim-Subscription-Key': subscriptionKey,
        'Content-type': 'application/json',
        'X-ClientTraceId': str(uuid.uuid4())
    }
  # serialize dictionary for use in Microsoft Translate API call
  requestBody = []
  for key in keyList:
    requestBody.append({'Text' : key})
  content = system.util.jsonEncode(requestBody)
  
  # send request to Microsoft Translate  
  conn = httplib.HTTPSConnection(host)
  conn.request ("POST", path + params, content, headers)
  # get response
  response = conn.getresponse()
  data = response.read()
  translatedData = system.util.jsonDecode(data)

  # create dictionary with all translated data
  dictOut = dict((key,{}) for key in keyList)
  for key, t in zip(keyList, translatedData):
    for language, translation in zip(langList, t['translations']):
      dictOut[key][language]=translation['text']

  # create output XML files      
  for lang in langList:
    fileName = baseName + '_' + lang + '_output' + extension
    

    dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance()
    db = dbf.newDocumentBuilder()
    dom = db.newDocument()
	# XML root
    root = dom.createElement('properties')
	#create 'comment' element with locale
    e = dom.createElement('comment')
    e.appendChild(dom.createTextNode('Locale: '+lang))
	# add 'comment' element to root
    root.appendChild(e)
	# add translations
    for key in keyList:
	  # create entry element
      e = dom.createElement('entry')
	  # add 'key' attibute with original text
      e.setAttribute('key', key)
	  # add translated data for the key entry
      e.appendChild(dom.createTextNode(dictOut[key][lang].decode('utf-8')))
	  # add 'entry' element to root
      root.appendChild(e)
	#close root
    dom.appendChild(root)
    
	# prepare xml file properties
    tr = javax.xml.transform.TransformerFactory.newInstance().newTransformer()
    tr.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, 'yes')
    tr.setOutputProperty(javax.xml.transform.OutputKeys.METHOD, 'xml')
    tr.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, 'UTF-8')
    tr.setOutputProperty(javax.xml.transform.OutputKeys.DOCTYPE_SYSTEM, 'http://java.sun.com/dtd/properties.dtd')
    tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "0")
	# convert XML data to a DOM source for writing to a file
    domSource = javax.xml.transform.dom.DOMSource(dom)
	# prepare FileOutputStream
    streamFile = FOS(fileName)
	# create StreamResult from FileOutputStream
    streamOut = javax.xml.transform.stream.StreamResult(streamFile)
	# write the DOM source to a file
    tr.transform(domSource, streamOut)
	# close the file
    streamOut.getOutputStream().close()

Disclaimer below, but here’s the TLDR version: It works for me, YMMV. Consider yourself warned.

Disclimer: While Jordan has made every attempt to ensure that the information contained in this post has been obtained from reliable sources, Jordan is not responsible for any errors or omissions, or for the results obtained from the use of this information. All information in this post is provided “as is”, with no guarantee of completeness, accuracy, timeliness or of the results obtained from the use of this information, and without warranty of any kind, express or implied, including, but not limited to warranties of performance, merchantability and fitness for a particular purpose. In no event will Jordan, his related partnerships or corporations, or the partners, agents or employees thereof be liable to you or anyone else for any decision made or action taken in reliance on the information in this post or for any consequential, special or similar damages, even if advised of the possibility of such damages.

4 Likes

Raising a thread from the dead here, but I just pushed a commit that will probably be in 8.0.2 which should help all of you out.

With this commit, exports of translations to property files now show non-ASCII characters in a human-readable way (ie. мир instead of \u043c\u0438\u0440)

Imports of property files with work either way (you can mix and match in the same file if you need to)

The net effect of that is you no longer need to have your translators deal with XML, which should make everyone happier.

What about v7.9? It will also get that?

I didn’t see any note on the ticket that we’re planning on backporting the feature.