Globalization tips for Domino Web Applications
Category NoneYou’ve been working on Domino applications for a long time, and are pretty confident in your methods. You follow best practices, attend conferences to learn new methods, and are confident that your code is well-structured and efficient. Now after an acquisition of your company, the boss steps in and compliments your hard work on the Domino extranet / customer service / help desk / whatever application you have brought to life. It goes something like this:
Boss: “I’m really impressed with the XYZ application. Our customers get a lot of value from it and the new CEO we got after the merger has even said it’s a great piece of work.”
You (swelling with pride): “Well thanks, it’s great to be appreciated!”
Boss: “Just one thing. Could you make it available in Spanish, Dutch and French?”
You: “Uh oh.”
Having worked on the application for several months, you know that a translation of the HTML, JavaScript, and anything else users might see is a big job. Picking through code isn’t one of your strong points, so how can you approach this in as orderly a fashion as you did your original application? Here are a couple tips that will help you on your way. And, whether you are writing Domino code for globalization or not, you may find these techniques useful for other reasons – they make your code more maintainable and easier to read while facilitating language independence.
First a nod to Domino Global Workbench, formerly a Lotus product (I can’t find a current reference) that performed similar functions in a very different way. I haven’t seen DGW in a very long time, so these tips obviously don’t take the product’s features into account.
Strings. That’s what we care about here – text strings that show up in your applications, whether as error messages, field labels, help text, or any other string that might be represented on the page. And, for web development, the current standard in globalization is the separation of those strings – also known as externalizing them – from the pages on which they appear. For the client or agent side of your applications (you ARE logging agents, right?), we use a different method to make strings easier to find and replace for new languages. But let’s start with web development, and externalizing strings from our HTML forms and pages.
One common method of externalizing strings from pages or forms is to include the strings as variables in an external JavaScript file. As you may know, there are four ways (off the top of my head) of using JavaScript “files” in Domino applications. You can:
- Save them to the file system
- Save them as file resources in the application
- Create JavaScript Script Libraries
- Create Page elements with JavaScript on them
Yes, there are a few other goofy ways to do this, like creating an image attachment but really using a JavaScript file (ah, the good old days), but let’s assume you prefer to use more recent methods. The basic idea is this – write your strings into an external JavaScript file, include it by reference in your HTML or Domino form/page, and then use the variable names when writing out the HTML. Let’s call these steps 1, 2 and 3:
Step 1, in an external file named strings.js:
var MSG_YOUR_NAME = "Your Name:";
var MSG_YOUR_EMAIL = "Your Email:";
var MSG_COMPLETE_FIELDS = "Please edit/complete the following field(s):";
var MSG_LOCATION = "Note your location:";
Step 2, in your HTML page or form:
<script type="text/javascript" src="/ strings.js"></script>
(Or if you’re using a Domino design element instead of straight HTML, you can include a script library directly)
Step 3, later in your page or form. For this example, we’re in the middle of building sHTML that will later be written out:
sHTML += '<div style="float:left;">' + MSG_LOCATION + ' ' + sLocation + '</div>';
Now while one major benefit here is the ability to change our variables to another language, we have discovered another side benefit that will likely result in using this method more often – the HTML is much, much shorter when the text strings are taken out! Also, it takes much less effort to pick through our code to make changes in wording.
If your application is truly destined for globalization, you'll want to use this method for all text, including field labels.
So on to the second point, that you probably run agents in your web applications that either write out content or write log entries. In that case, the method is a little different and not quite as portable, but with the goal of simplicity in translation, the idea is the same.
It’s still about strings. This example is from some agent code that writes to an error log, which may need to be read on the web, or might be viewed in the Notes client. At the top of agent code, we choose to use constants for our strings. This way, we can have a translator change the text values in one place and deploy the application to another language. If you think about it for a moment, you can make this agent call configurable, to determine which language version of the same agent to call based on a role, configuration document, list, etc.
'*************************************************
'Globalization constants
Const ERR_VIEW_NOT_FOUND = "The following view could not be found: "
Const ERR_NO_PLACENAME = "No PlaceName supplied for initialization."
Const ERR_PLACE_SETTINGS = "Place settings document not found."
'**************************************************
Then later in the agent, we reference the constants instead of writing our error messages:
Public Sub New(p_sPlaceName As String)
On Error Goto ErrorHandler
If Trim(p_sPlaceName) <> "" Then
Set db = New NotesDatabase("", "Main.nsf")
If db.IsOpen Then
Set docSettings = viewSettings.GetDocumentByKey("RoomSettings", True)
If Not(docSettings Is Nothing) Then
Me.m_sPlaceTitle=docSettings.GetItemValue("h_Name")(0)
Else
Call Me.RaiseError(ERR_PLACE_SETTINGS)
End If
Else
'error db not open
Call Me.RaiseError(ERR_DB_NOT_OPEN & "'Main.nsf' - '" & p_sPlaceName & "'")
End If
Else
'error PlaceName not supplied
Call Me.RaiseError(ERR_NO_PLACENAME)
End If
Done:
Exit Sub
ErrorHandler:
'Report Error
Call Me.RaiseError(Error$)
Resume Done
End Sub
So there are a couple quick tips to help when the boss comes calling with requests for new text, or new languages. And you benefit because your code is even more streamlined, easier to read, and more modular.

Comments
Which is not practicable, if your website needs to conform to the W3Cs WAI guidlines and/or the U.S. Section 255 guidelines and Section 508 standards.
Point 6.3 of the Web Content Accessibility Guidelines 1.0 demands: "Ensure that pages are usable when scripts, applets, or other programmatic objects are turned off or not supported."
Posted by Martin Leyrer At 05:18:20 PM On 04/12/2007 | - Website - |
Posted by Rob Novak At 05:48:02 PM On 04/12/2007 | - Website - |
If we use Object-Orientation and the "late binding" concept, we can improve the agent string externalization, and we'll get to a solution pretty much closer to the javascript one.
Pretend we have 3 different script libraries, with the following names:
slStrings-en-US
slStrings-pt-BR
slStrings-es-ES
Each one has a class like this, with the proper string translations:
Public Class StringsProvider
Public Property Get ERR_VIEW_NOT_FOUND As String
ERR_VIEW_NOT_FOUND = "The following view could not be found: "
End Property
Public Property Get ERR_NO_PLACENAME As String
ERR_NO_PLACENAME = "No PlaceName supplied for initialization."
End Property
Public Property Get ERR_PLACE_SETTINGS As String
ERR_PLACE_SETTINGS = "Place settings document not found."
End Property
End Class
And, somewhere in the agent you have the following routine:
'Declarations
Public TempObj As Variant
Public Function GetStringsProvider( strLocation ) As Variant
Set TempObj = Nothing 'Asure there is no trash here
Execute {Use "slStrings-} & strLocation & {"
Sub Initialize
Set TempObj = New StringsProvider()
End Sub
}
Set GetStringsProvider = TempObj
Set TempObj = Nothing
End Function
And then change the agent a little bit to use the previous stuff
Dim objStrings As Variant
Set objStrings = GetStringsProvider( "pt-BR" ) 'better to identify the location programmatically
'... Suppressed the code for simplicity
Call Me.RaiseError(objStrings.ERR_PLACE_SETTINGS)
Posted by Ricardo Lucio At 07:32:30 AM On 04/13/2007 | - Website - |
The problem with having so many Domino blogs around is people forget where they are...!
Rock Star: Rob
Geek: Rocky
Posted by Rob Novak At 07:54:43 AM On 04/13/2007 | - Website - |
To the point of the post, I have an alternate suggestion for externalizing the strings: Keep them in documents and use computed text to turn them into Javascript variables. The benefit is that you can update the translation documents without having to touch the code (in SOX-compliant areas, this is huge). The cost is that the variables won't be cached by the browser, as they would if they were in a JS file. So there's a bit of a performance hit, but the maintenance benefit outweighs it, at least to my mind.
Posted by Rob McDonagh At 10:21:53 AM On 04/13/2007 | - Website - |
It's the number of choices that makes this a great platform, even if we do have to explain it to each other!
Posted by Rob Novak At 11:03:45 AM On 04/13/2007 | - Website - |
You could stuff the translations into a view and use ?Readviewentries with the JSON parameter to get the strings as ready JS Object.
Posted by Stephan H. Wissel At 08:41:08 AM On 04/16/2007 | - Website - |
1. always make a check if the desired language resources are available - and keep at least an english message stating that translations can't be found in the code. This way, you always are able to show the user why things don't look as expected...
2. Don't assume that translations have the same kind of sentence composition. The sequence of parameters in sentences might change. What you need is a named parameter replacement scheme enabling you to change the order of substitutions in translated sentences. I don't know if this cuts the meat for languages with different basic concepts (japanese/chinese etc.), but it certainly works for all alphabeth-based languages.
Posted by Peter Hochstrasser At 05:45:36 AM On 04/23/2007 | - Website - |
Posted by Venugopal Reddy At 07:22:40 AM On 05/07/2007 | - Website - |