« A nice-to-have developer extension: MRI bookmarklet from Westciv | Main | Which is the best feed reader? »

Internationalization: Integrating language files into your ColdFusion application

While thinking about which features to include in one of my applications, I decided it would be valuable to customers (and a good mental exercise for me) to provide the cabability for the app to support different languages, and to support the ability for the user to add new languages of their own.

I will admit that while I did look around a bit for articles on how to achieve this, I didn't look very hard, because the solution seemed readily apparent...

The broad strategy of making your application work with multiple languages is to replace all of the static display text in your source with ColdFusion variables which will be interpreted into the user's selected language. By "display text", I refer to all copy that users read for instruction, navigation, etc., as opposed to text that makes up comments, code, or URLs that they don't see. The lookup for the variables will reside in a language file.

I found the easiest way to organize the data in the language file was this: first, I created a structure called stLang. Then, I nested another structure in stLang for each page or major function that the user would see, such as stLang.login, or stLang.preferences. Finally, I added a key for each piece of text in that page or function. Sometimes you can afford to embed a whole paragraph or sentence in each key; at other times, it's more efficient to store just a small phrase that you use in other places do that you don't have multiple keys with the same value. Use the <cfsavecontent> tag to help you use dynamic strings. Here's an example of the English.cfm language file. Note the <cfsilent> tags; they'll be explained later.


<cfsilent>
<cfset stLang = StructNew() />

<cfset stLang.terms = StructNew() /><!--- Common terms --->
<cfsavecontent variable="stLang.terms.appName">My Application</cfsavecontent>
<cfsavecontent variable="stLang.terms.copyright">Copyright ©</cfsavecontent>
<cfsavecontent variable="stLang.terms.for">for</cfsavecontent>
<cfsavecontent variable="stLang.terms.save">Save</cfsavecontent>
<cfsavecontent variable="stLang.terms.close">Close</cfsavecontent>
<cfsavecontent variable="stLang.terms.cancel">Cancel</cfsavecontent>

<cfset stLang.login = StructNew() /><!--- For the login page --->
<cfsavecontent variable="stLang.login.pageTitle">Login</cfsavecontent>
<cfsavecontent variable="stLang.login.welcome"><cfoutput><p>Welcome to #stLang.terms.appName#</p></cfoutput></cfsavecontent>
</cfsilent>

I stored my language files in a specific directory named /lang/ and wrote my preferences page to read the contents of that directory. Any .cfm file in it will be added to the list of languages offered to the user in their preferences page; this way, new languages can be added as necessary without changing any application code. The user's language preference is saved so that whenever they log back in, we can just include their preferred language file in Application.cfc and save stLang as SESSION.stLang. Then you just refer to variables throughout your application as needed, such as SESSION.stLang.login.welcome for the welcome message on the login page.

So that works for all of your .cfm files. But there are other files and media types on your site that may provide readable content to your user-- specifically, images and JavaScript files. It's easy to take care of images; just make their source into a variable in your language file, or make subdirectories in your /images/ folder that correspond to the language names, and change your image source accordingly in your code. But getting dynamic content to into JavaScript is a bit more of a challenge. One obvious solution is to change the extension of your .js files to .cfm, and then you can add language variables to them. Yet I find this to be a somewhat less preferable solution, since I'd rather my script files be cached by the browser to make page loading go faster.

So, I added JavaScript code to the bottom of the language file and called the language file in a script tag. Here's a sample of the script code in the language file:


<cfsilent>
<cfset stLang = StructNew() />

<cfset stLang.terms = StructNew() /><!--- Common terms --->
<cfsavecontent variable="stLang.terms.appName">My Application</cfsavecontent>
<cfsavecontent variable="stLang.terms.copyright">Copyright ©</cfsavecontent>
<cfsavecontent variable="stLang.terms.for">for</cfsavecontent>
<cfsavecontent variable="stLang.terms.save">Save</cfsavecontent>
<cfsavecontent variable="stLang.terms.close">Close</cfsavecontent>
<cfsavecontent variable="stLang.terms.cancel">Cancel</cfsavecontent>

<cfset stLang.login = StructNew() /><!--- For the login page --->
<cfsavecontent variable="stLang.login.pageTitle">Login</cfsavecontent>
<cfsavecontent variable="stLang.login.welcome"><cfoutput><p>Welcome to #stLang.terms.appName#</p></cfoutput></cfsavecontent>
</cfsilent>

<cfif ListLast(CGI.SCRIPT_NAME, '/\') eq ListLast(GetCurrentTemplatePath(), '/\')><cfcontent type="text/javascript"></cfif>

var language = new Object; // Defines an object which will hold all language terms

// Common terms
language.terms = new Object;
language.terms.appName = '<cfoutput>#JSStringFormat(stLang.terms.appName)#</cfoutput>';
language.terms.pleaseFill = 'Please fill out the following fields:';

// Login messages
language.login = new Object;
language.login.username = 'Username';
language.login.password = 'Password';

Now the reason for the <cfsilent> becomes apparent: you don't want the line breaks from your CFML code showing up when the file is called as a JavaScript. Also note the <cfif> code that looks to see if the page is being called directly by looking to see if the language file is called in CurrentTemplatePath(). It sets the correct MIME type for a script in this event.

So call the language file as a script like so (given that you have the user's language preference set as SESSION.languageFile):


<script language="JavaScript" src="/lang/#SESSION.languageFile#"></script>

This will ensure that you have a JavaScript object called language, and you can refer to it in all other JavaScript code. This way, all of your JavaScript code can be language neutral, just like your CFML code.

So that's how I've achieved multiple-language support for my application. It's my first time, so there may be ways that this can be done better, but I've looked at PHP applications like Horde and can see that they do roughly the same thing. If anyone has any comments, critiques, or alternative methods I'd be happy to discuss them in the comments.

Comments (1)

Its is informative.

Post a comment


Type the characters you see in the picture above.