« Collaboration University 2007: Two Times Two! | Main| Lotusphere 2007 Session Ratings »

Globalization tips for Domino Web Applications

QuickImage Category None

You’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:
  1. Save them to the file system
  2. Save them as file resources in the application
  3. Create JavaScript Script Libraries
  4. 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

Gravatar Image1 - One common method of externalizing strings from pages or forms is to include the strings as variables in an external JavaScript file.

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."

Gravatar Image2 - @Martin, agreed. This tip, like most modern methods, doesn't apply when accessibility regulations are a concern. I have had to deal with that concern recently in another project.

Gravatar Image3 - Very nice RockyLet me give my contribution to this nice tip.
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)

Gravatar Image4 - @3 LOL

The problem with having so many Domino blogs around is people forget where they are...!

Rock Star: Rob
Geek: Rocky


Gravatar Image5 - @4 But you are, sort of, a Rocky, Rob! You're the Lotus Rock(y) Star, right? :D

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.

Gravatar Image6 - @Rob - agree and this points out the many options and for that matter mindsets we have as developers in Domino, QP, etc. In some cases we don't have computed text available to us, and others we want to switch languages rapidly. Some of us are thinking of Domino development from a pure web development context and others from more of a Notes Design element context.

It's the number of choices that makes this a great platform, even if we do have to explain it to each other!


Gravatar Image7 - While you are on it...
You could stuff the translations into a view and use ?Readviewentries with the JSON parameter to get the strings as ready JS Object.
stw

Gravatar Image8 - A few more concerns: In ibex DomainManager, back in '96 on Notes 3, we added the following features to our Notes databases:

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.

Peter

Gravatar Image9 - Very Nice.................

Calendar

Rock On With Me and SNAPPS

Join me and the great team at SNAPPS at these upcoming events:

IamLUG
I am Lotus User Group - August 2-4, St. Louis

Collaboration University
London and Chicago - September 21-23 and 27-29 respectively. That's right, London goes first!

The events have very limited capacity so signing up as soon as possible is recommended. Hope to see you there!

Be With the Band

Follow me on Twitter!


Opt in to receive Rob's semi-regular newsletter about Quickr, Sametime, Free Stuff and Conferences. Just enter your email address below, you can unsubscribe at any time.

Subscribe to my newsletters...
Email:

On With The Show

Here is a list of the SNAPPS templates for Lotus Quickr and other free resources on QuickrTemplates.com:
Templates:
QContacts
QIdeas
QIssues
QMeeting
QPhotos
QPresent
QProject
QSite
QSurvey

Utilities:
AnyPlace SiteMap
AnyPlace ServerMap
AnyPlace Designer for Dreamweaver

Free Apps:
PandaBear: Cross-Platform File Management
Flippr: Lightweight Quickr Admin Client
SnappFiles: iPhone Client for Quickr, Filenet, ICM...

Downloads: 104,397
Countries: 161
Read about the templates in Intranet Journal

Search

Googles

  • No Search Referers