Localization of Email Campaign Content From Eloqua

Eloqua is a marketing automation platform, allowing marketers to easily create campaigns consisting of emails, landing pages, social media etc. via its ‘campaign canvas’.

Campaigns can be created via a WYSIWYG interface, allowing you to visualize marketing campaigns easily as you build them. It also integrates with CRM tools, automatically passing any lead data generated onto your sales staff.

My interest in Eloqua, and specifically its API, relates to the localization of email campaign content. This can be achieved manually by exporting the created email (as a standalone HTML file), then re-importing the localized versions post translation, creating a new version of the original, one for each language you have localized for.

Manual exporting of email content for localization is of course a perfectly valid approach, but the more languages you have, the more manual steps in this process, and the longer it takes, potentially tying up a resource.

The Eloqua REST API can be used to easily reduce the transactional costs related to localization of email campaign content. Using the API, you can quite easily automate the extraction of email content, and potentially send this content directly to your Translation Management System (TMS) such as GlobalSight or WorldServer, or straight to a translation vendor in the absence of a TMS.

The API documentation is pretty good. I also came across this samples project on GitHub released under the Apache license which I was able to use to knock up a proof of concept pretty quickly. It’s written in C# and includes functions to manipulate most asset types in Eloqua.

The email sample code in this library illustrates how to create, edit, and delete emails in Eloqua via the REST API. For example, it’s this easy to grab an Object that represents an email in Eloqua:

EmailClient client = new EmailClient(EloquaInstance, 
                         EloquaUsername, EloquaPassword, 
                         EloquaBaseUrl);

 try
 {
     Email email = client.GetEmail(emailID);
     return email;
 }
 catch (Exception ex)
 {
     // Handle Error...
 }

Some notes:

  • When retrieving the Email Object which represents an email in Eloqua, you need to specify the ID of the email in Eloqua. For automating the localization process, it could be difficult to determine this without user input. What I plan on doing is providing a nice UI so that users see only the emails that they have created in Eloqua (i.e. not all emails created ever), and can click on one and submit it for translation in one click.
  • The Email Object also contains other useful metadata like when the content was created, when it was last updated and by whom, the encoding, and the folder in which this email resides in Eloqua, useful for when you want to upload the localized versions.

So, that’s how easy it is to automate the retrieval of email content from Eloqua. The library I referenced also has support for other asset types like landing pages etc.

Next I plan on using ASP.NET Web API to turn this library into a HTTP service I can use to talk to Eloqua from other applications, such as the application that manages content submission/retrieval from our TMS.

GlobalSight Web Services API: Manipulating Workflows Programmatically

This is part four of my series of posts on the GlobalSight Web Services API. See below for the previous posts:

Here, I’d like to cover how you can manipulate workflows in GlobalSight via the API. For example, dispatching a workflow, accepting tasks in a workflow, or completing or moving workflows on to the next step.

This can be useful if you want to automate specific steps, or if you want to use an interface other than GlobalSight when dealing with workflows.

There are a couple of relevant functions that are pre-requisites when dealing with workflows programmatically:

  • getJobAndWorkflowInfo – This returns details about workflows in a particular job. The ID of the job in question is passed as a parameter to this function. This will return an XML response, detailing as well as the workflow information, some basic information about the job. We need this to get the ID of the workflow which we want to work with, within a particular job.
  • getAcceptedTasksInWorkflow – This will return the task information for a workflow, given the workflow ID (which we would have got from getJobAndWorkflowInfo). With the XML response here, we’ll be able to search for specific tasks in a workflow, and get the task ID – this is what is required in order to manipulate specific tasks in a workflow.

From the above, (I leave the parsing of the XML response as an exercise to the reader), we can begin to perform workflow tasks by using the task ID.

// Accept task
string acceptTask = client.acceptTask(auth, "63781");
Console.WriteLine("Accept Task Response: " + acceptTask);

// Send this worfklow to the 'Write to TM' step
string completeTask = client.completeTask(auth, "63781", "Write to TM");
Console.WriteLine("\n\nComplete Task Response: " + completeTask);

The above is a very simple example of accepting a particular task in a workflow (using the workflow ID), and sending it to the next step in the workflow via ‘Write to TM’.

Write to TM‘ here is the actual text on the arrow in the workflow diagram. I found this syntax strange, but it seems to work.

Capture

It should be noted that the user under which you are logged into the web service with must be on the list of those allowed to accept the task you are trying to accept, you will receive an error otherwise.

The GlobalSight Web Services API documentation has much more information the the types of actions you can perform on workflows, but the above should get you started.

GlobalSight Web Services API: Job Creation

This is the third in my series of posts on the GlobalSight Web Services API. See below for the previous posts:

In this post, I’m going to cover how to actually create jobs in the GlobalSight system via the Web Services API. Creating jobs via the user interface works fine, but if you want to automate the process, this is fully supported via the API. This can be useful if for example you wanted to create a better experience for creating jobs, or a totally different interface – useful if you have people not experienced with GlobalSight who may want to submit content translation or review jobs.

I’ll assume you’re all setup to interact with the API from C# (see the first post above for a basic introduction if you are not).

There are a couple of API calls relevant to job creation:

  • getFileProfileInfo – This returns the list of File Profiles currently available in the system. The response is XML format, listing all File Profiles available in the system, each having an ID, Name and Description. The File Profile is a required parmeter to both the uploadFile and createJob functions.
  • uploadFile – This facilitates upload of a single file to GlobalSight (note it does not actually create a job). This needs to be called once per file.
  • getUniqueJobName – This function essentially takes a job name, and adds a nine digit unique ID to the name. Each job in GlobalSight must have a unique name. If you already have some unique identifier in your job name, you will not need to call this function, but otherwise it is useful for ensuring that there aren’t any clashes between job names.
  • createJob – This is the function that actually creates the job in GlobalSight.

Let’s look at some simple code for job creation using the above listed functions.

			GS_API.AmbassadorService client = new GS_API.AmbassadorService();

			string jobName = "Demo Job (Ignore)";
			string textFilePath = @"C:\Users\Jimmy\Desktop\strings.txt";
			string fileProfileID = "37";
			byte[] fileContents = System.IO.File.ReadAllBytes(textFilePath);

			// Authenticate
			string auth = client.login("TestUser1", "password");

			// Here you would get the file profiles, and find the apt. one for this job
			// I'll leave this to the reader as an exercise, I've assigned a variable above

			// Next, lets ensure uniqueness of our job name
			string uniqueJobName = client.getUniqueJobName(auth, jobName);

			// Upload the file
			client.uploadFile(auth, uniqueJobName, "/files/strings.txt", fileProfileID, fileContents);

			// Create the Job - ensure to use the same job name as your call the uploadFile
			client.createJob(auth, uniqueJobName, "Demo Job", "/files/strings.txt", fileProfileID, "");

A couple of points to note:

  • Each file you upload needs to be converted to a byte array. C# provides the handy function I’ve used above for this purpose.
  • The third parameter to uploadFile, (in this case ‘files/strings.txt‘), is the location to upload the file on the GlobalSight server. There are a few folders created by default for any job, the language code, ‘webservice‘ which indicates the files were uploaded via the API, and a folder named after the job ID. The parameter above is the directory structure inside the job ID folder, this is whatever you want it to be. This is useful, as the files are exported in that same structure post translation, so you can for example retain the directory structure of a translation kit if you so wish. Here’s an example of how the file we uploaded above is stored on the GlobalSight server:

Capture

 

  • The job name you pass to createJob, must be identical to the one you passed to uploadFile. This is how GlobalSight knows which files relate to this job.
  • The final parameter in createJob is the target languages. Leave this empty (as I have above) and GlobalSight will assign all the languages contained in the Localization Profile that is associated with the File Profile you have specified.

That’s it, after you have run the above code, your job is now created in GlobalSight.

This was a very basic introduction to job creation in GlobalSight, showing how a single file can be submitted via the web services API.

I haven’t covered items such as error handling, or even creating jobs that have multiple files, not just one as in my simple example above – I will perhaps cover this in a future post.

GlobalSight Web Services API: Automate Translation Memory Upload

This is the second in my series of posts on the GlobalSight Web Services API. For a brief introduction, and the steps to get setup using the API from C#, see my previous post.

Translation Memories, in TMX format, can take a long time to upload to GlobalSight via the user interface, especially if you have a lot of large ones.

This can be quiet easily automated (and run overnight for example) via the GlobalSight Web Services API.

First, you need to call uploadTmxFile, which just uploads the TMX file to the GlobalSight server, then call importTmxFile, which does the actual import to the system:


GS_API.AmbassadorService client = new GS_API.AmbassadorService();

string demoTmxLocation = @"C:\TMX\demo.tmx";
string tmName = "Main";

// Authenticate
string auth = client.login("TestUser1", "password");

// We need to convert the TMX file to a byte array
byte[] tmxAsBytes = System.IO.File.ReadAllBytes(demoTmxLocation);

// Next, upload the TMX
client.uploadTmxFile(auth, "demo.tmx", tmName, tmxAsBytes);

// Finally, import the TMX
// The final parameter here can be merge, overwrite, or discard
client.importTmxFile(auth, tmName, "merge");

We found this to be up to 25 times faster than uploading TMX files via the user interface, outside of the obvious benefit of not tying a resource up uploading TMX files for a few days.

Next up, I’ll outline how to create translation/review jobs in GlobalSight using the API.

Interacting with the GlobalSight Web Services API from C#

GlobalSight is an open source Globalization Management System (GMS). It’s main purpose is to automate the flow of content to translators, and essentially streamline many of the tasks involved in the translation workflow, scoping, project management, translation, review etc. as well as centralizing translation memories.

Similar GMS systems would be SDL WorldServer (formally Idiom WorldServer before being acquired by SDL in 2008), and SDL TMS.

What differentiates GlobalSight (owned by WeLocalize), is that it is open source. The source code is freely available. It can be customized as you see fit. It also provides a very powerful web services API, that you can use to integrate other systems in your translation workflow with GlobalSight, that’s what I want to introduce here, specifically the steps to get setup from C# to interact with this API.

I’m going to assume you already have an instance of GlobalSight setup, I’d imagine you wouldn’t be interested in this API otherwise.

Setup Steps

  • First off, create a new C# Visual Studio project, a console application will suffice for this tutorial.
  • Next, we need to add a Service Reference to the API. To do this, follow the steps here, using the following URL:

https://<YOUR_SERVER_URL/globalsight/services/AmbassadorWebService?wsdl

Note – The service is called ‘AmbassadorWebService‘ because GlobalSight itself was previously called Ambassador, when it was owned by a company called GlobalSight. The product was renamed GlobalSight after it was taken over by WeLocalize. If you get to the point where you are looking at the (rather large) code base, you will see it littered with references to Ambassador.

  • Once you successfully add the service reference, try to build your project – you will notice it will fail (at time of writing at least anyway):

CaptureThere are duplicate entries in Reference.cs for these two functions, getAttributesByJobId and getProjectIdByFileProfileId.

To fix this issue, just double click on each of the above errors, and comment out the duplicate entries in Reference.cs. This will allow your project to build, but is a pain, as you need to do it each time you update your service reference.

Now that your project successfully builds, you are ready to use the API.

Authentication

Before trying to authenticate with the web service, you may need to do some manual configuration on your GlobalSight instance. The IP Filter feature is enabled by default – this will only allow IP’s on a white list to interact with GlobalSight via the API. There are two options here:

  • Disable the IP filter completely – not recommended.
  • Add the necessary IP ranges to the white list.

See the ‘IP Filter’ section of the web services documentation for more information on this.

Once you complete the above, we should be able to connect to the API via some C# code.

Before calling any operations, the login function must be called with a valid GlobalSight account. This function returns an authentication token which must then be passed as a parameter to all subsequent API calls.

Let’s connect, and call a simple function, getAllUsers, that will give us the list of all user currently in the system, and write it out to the console so that we can see the response:

GS_API.AmbassadorService client = new GS_API.AmbassadorService();

string auth = client.login("TestUser1", "password");
string userinfo = client.getAllUsers(auth);
Console.WriteLine(userinfo);
Console.ReadLine();

If you have done everything correctly, you will see an XML response in your console window detailing all the users currently setup in the system. Most API calls to retrieve information return an XML response, it’s just a matter of parsing it, and doing whatever you want with it then.

Conclusion

Here are my feelings so far, having used this API for the past 3 months to automate different tasks:

  • You can’t rely on the documentation on the Wiki – some function definitions are out of date, some now have extra parameters etc. There is not enough information in the documentation about what each call actually returns (i.e. the format of the XML). I found myself having to run commands to see what they actually return. The API documentation needs some love.
  • Some API call sequences (e.g. for job creation) are really difficult to figure out, e.g. you need to call uploadFile, then createJob. Again, making this clear in the documentation would be better.
  • Some functions expect paramters in XML format, but no example of this format is give. Documentation!
  • For an open source project, there doesn’t seem to be any community. The forums on globalsight.com are not particularly active, and run some really old forum software that is extremely frustrating to use. Maybe there is a community, and I’m just not aware, but it certainly looks like they don’t hang out at globalsight.com.
  • All this aside, the web services API is extremely powerful, and I seriously recommend looking into it if you use GlobalSight – some functions can save you days – e.g. upload of translation memories via the API, as opposed the web interface.

This covered a basic introduction to interacting with the GlobalSight web services API from C#.

In a future post, I’ll cover how to actually create GlobalSight jobs via this API. If there’s anything else you’d like to see covered in a future post, leave a comment below, and I’ll see what I can do.

Static Analysis for finding I18N Issues

Static analysis tools are useful for quickly identifying issues with your code. Recently, my team has been evaluating Globalyzer, a static analysis tool for finding software internationalization issues. Globalyzer will find such issues as hard coded strings, or hard coded date/time formats. Our aim is to move the discovery of I18N issues from the localization QA phase, upstream to the development phase.

My first impressions are good. Globalyzer is quick to setup, easy to use, and scans code unbelievably quickly. I’ve scanned a project with 350,000 lines of code in less than a minute.

The key to an effective rollout of Globalyzer is developing rule sets per product. Rule sets essentially define the scanning criteria for a code base. For example, for a C++ project, we would start with the built in rule set for C++. Then after an initial scan, we analyze the results, and we may want to update the rule set. For example, we may want to configure the rule set to ignore calls to any logging functions, since we don’t localize log messages.

I’m hoping that we get the following usage from Globalyzer:

  • Globalyzer should help us get involved earlier in the development cycle.
  • Lots has been written about Agile localization. Globalyzer should help us certify sprints as I18N ready, for later localization.
  • We should find any serious I18N issues early in the project – thus reducing fire-fighting later on.

Longer term, I’d like for us to look at integrating Globalyzer with our build systems. I’ll be posting here periodically on our progress, and my experiences with using static analysis tools to find code level internationalization issues.