Previous Page TOC Next Page See Page



- 17 -
A Language-Switching Interface


Given the increasingly international character of the Internet, multilingual Web sites are becoming increasingly common.

However, creating an effective mechanism for deploying multilingual sites can be difficult. Generally, if a site offers all its content in multiple languages, then it needs to provide methods for users to switch languages at their current location in the document tree without having to start again from the top level.

There are several ways this can be done:

In this chapter, you will be pursuing this last option.

Basic Requirements


The scripts you will be writing in this chapter are designed to enable Web authors to easily deploy multilingual sites without complex scripting at the client or server end and without requiring tedious building of manual cross-language links for the whole site.

Your language-switching tools need to provide the following capabilities:

In order to effectively implement these capabilities, it is important to first define the environment in which the scripts must be used:

There are many sites on the Internet currently using different sorts of language-switching mechanisms for handling multiple languages. Figure 17.1. is an example of a multi-lingual site.

Figure 17.1. The Bahá'í World (http://www.bahai.org/) uses a multi-frame design and a graphical menu to implement a multi-lingual site.

The Scripts


Three script libraries make up the language-switching tool kit you are developing:

The source code for these three script libraries follows.

Language-Switching Functions


The first script library consists of the functions that actually perform the language switching. These functions need to be included in the parent frameset of a site using frames or in each document in a non-frames site.

// Declare Constants
var dialog = true;
var noDialog = false;
var complete = true;
var notComplete = false;
var closeChooseDialog = true;
var currentDocument = -1;
// Declare Global Variables
var chooseDialog;
var language = new Array();
var currentLanguage;
var wantWaitDialog;
var waitDialog;
//  Change Language Function - takes zero, one or two argument
function changeLanguage() {
   // Check if there are arguments --
   // This determines what type of change
   // we are making.
   if (changeLanguage.arguments.length > 0) {
      // A language is passed to the function,
      // so we are doing a direct change.
      var newLanguage = changeLanguage.arguments[0];
      // Check if we need to close a dialog box
      if (changeLanguage.arguments.length > 1) {
         chooseDialog.close();
      }
      // Check if language choice is valid
      if (newLanguage < 0 || newLanguage >= language.length) {
         return;
      }
   } else {
      // No language is passed, so let's ask for one.
      selectLanguage();
      return;
   }
   // Check if this is the current language or not.
   if (currentLanguage == newLanguage) {
      // It is the current language.
      window.alert("You are already reading " + language[newLanguage].displayName + ".");
      return;
   }
   // Check if dialog box is needed.
   if (wantWaitDialog) {
      // Open the box and display a wait message
      waitDialog = window.open("","wait","toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=1,resizable=0,copyhistory=0,width=300,height=200");
      waitDialog.document.open("text/html");
      waitDialog.document.writeln('<HEAD>');
      waitDialog.document.writeln('<TITLE>Please Wait ... Changing to ' + language[newLanguage].displayName + '.');
      waitDialog.document.writeln('</TITLE></HEAD>');
      waitDialog.document.writeln('<BODY BGCOLOR="black" TEXT="yellow">');
      waitDialog.document.writeln('<H1><DIV ALIGN=CENTER>');
      waitDialog.document.writeln('Please Wait ... Changing to ' + language[newLanguage].displayName + '.');
      waitDialog.document.writeln('</DIV></H1>');
      waitDialog.document.writeln('</BODY>');
      waitDialog.document.close();
   }
   // Check if we have frames
   if (self.frames.length < 1) {
      // No frames - just change the current document,
      // but first check if the document is market changeable
      if (change) {
         changeDocument(-1,newLanguage);
      }
   } else {
      // We have frames -- loop throught them and make changes
      for (frameNum = 0; frameNum < self.frames.length; frameNum ++) {
         // Only make changes if the document is marked changeable
         if (self.frames[frameNum].change) {
            changeDocument(frameNum,newLanguage);
         }
      }
   }
   // Check if dialog box needs closing
   if (wantWaitDialog) {
      // Close the box
      waitDialog.close();
   }
   // Reset the current language
   currentLanguage = newLanguage;
}
// Switch a Document --  Takes two arguments
function changeDocument(frameNum,newLanguage) {
   // Are we changing the current document?
   if (frameNum == currentDocument) {
      // The current document is changed
      self.location = replace(self.location.href,language[currentLanguage].fileName,language[newLanguage].fileName);
   } else {
      // Change a frame
      self.frames[frameNum].location = replace(self.frames[frameNum].location.href,language[currentLanguage].fileName,language[newLanguage].fileName);
   }
}
// Replace text in a string -- Takes three arguments
function replace(target,oldTerm,newTerm) {
   var work = target;
   var ind = 0;
   var next = 0;
   // Not case sensitive, so change everything to lowercase
   oldTerm = oldTerm.toLowerCase();
   work = target.toLowerCase();
   // Search for math string in original text -- if we find it, make the change
   while ((ind = work.indexOf(oldTerm,next)) >= 0) {
      target = target.substring(0,ind) + newTerm + target.substring(ind+oldTerm.length,target.length);
      work = work.substring(0,ind) + newTerm + work.substring(ind+oldTerm.length,work.length);
      next = ind + newTerm.length;
      if (next >= work.length) { break; }
   }
   return target;
}
// Open Language Selection Dialog Box Function
function selectLanguage() {
   // Open the box and display choices
   chooseDialog = window.open("","choose","toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=1,resizable=0,copyhistory=0,width=300,height=350");
   chooseDialog.document.open("text/html");
   chooseDialog.document.writeln("<HEAD>");
   chooseDialog.document.writeln("<TITLE>Select a Language</TITLE>");
   chooseDialog.document.writeln('<SCRIPT LANGUAGE="JavaScript">var mainWindow = window.opener;</SCRIPT>');
   chooseDialog.document.writeln("</HEAD>");
   chooseDialog.document.writeln("<BODY BGCOLOR=black TEXT=yellow>");
   chooseDialog.document.writeln("<DIV ALIGN=CENTER>");
   chooseDialog.document.writeln("<H1>Select a Language:</H1>");
   chooseDialog.document.writeln(buildMenu(dialog,notComplete));
   chooseDialog.document.writeln("</BODY>");
   chooseDialog.document.close();
}
// Build a menu Function -- Takes two arguments
function buildMenu(dialog,complete) {
   var menu = "<TABLE CELLPADDING=5>";
   menu += "<TR><TD BGCOLOR=cornsilk ALIGN=center><BR>";
   menu += "<FORM NAME=chooseForm>";
   menu += "<SELECT SIZE=5 NAME=languageChoice>";
   // Build the option list
   for (thisLang = 0; thisLang < language.length; thisLang ++) {
      // If not the current language, or if complete list 
      // is indicated, add to the option list
      if (thisLang != currentLanguage || complete) {
         menu += "<OPTION VALUE=" + thisLang + ">" + language[thisLang].displayName;
      }
   }
   menu += "</SELECT>";
   menu += "<P>";
   menu += '<INPUT TYPE=button VALUE="Change Language" onClick="if (document.chooseForm.languageChoice.selectedIndex >= 0) { ';
   // If this is a dialog box menu, prepare
   // call across windows, otherwise call parent frame
   if (dialog) { menu += 'mainWindow'; } else { menu += 'parent'; }
   menu += '.changeLanguage(document.chooseForm.languageChoice.options[document.chooseForm.languageChoice.selectedIndex].value';
   // If this is a dialog box menu, then make sure it gets closed
   if (dialog) { menu += ',' + closeChooseDialog; }
   menu += ') }">';
   menu += "</FORM>";
   menu += "</TD></TR></TABLE>";
   return menu;
}
// Language Object Definition Function -- Takes two arguments
function languageObj(fileName,displayName) {
   this.fileName = fileName;
   this.displayName = displayName;
}

This set of functions is the center of the language-switching toolkit. It should be saved in a file name language.js.

Due to its relative complexity, this script requires some detailed analysis.

Variable Declaration

The scripts starts with declarations of global constants and variables. The global constants are used later in the script to make function calls and comparisons and assignments more readable.

The global variables are used for various purposes:


The changeLanguage() Function

The changeLanguage() function is the heart of the toolkit. It is where all the key decisions are made when a language change request is made.

The functions starts by determining how many arguments are passed to the function and, based on that, follows one of the following courses of action:

If the function gets this far, then a language was passed to the function and it is a valid language. The first thing to do before changing languages is to check if the language is the same language as is currently displayed; if it is, then no change should take place. The user is informed with an alert() dialog box, after which the function exits.

Assuming the selected language is not the same as the current language, then the function checks if a wait dialog box is specified in the configuration variable wantWaitDialog. If it is, the dialog box is displayed and the global variable waitDialog is set to point to the window object for the dialog box.

After the dialog box is opened (if needed), then the function is ready to change languages. The way in which this change is made depends on whether or not the site is operating as a frameset or as a single document. This is determined by looking at the value of self.frames.length. If it is greater than zero, you know you are dealing with a frameset.

If the site is not frames-based, then you need to simply switch the current document. First, check to see that the configuration indicates the document is changeable; if it is, call changeDocument(), indicating that you are changing the current document (as opposed to one frame of the frameset).

If the site is frames-based, then things are a little more complicated. A for loop is used to iterate through each separate frame and, if it is marked as changeable, then changeDocument() is called and the index of the frame is passed to it.

After the changes have been made, the wait dialog box is closed, if necessary, and currentLanguage is set to the new language.

The changeDocument() Function

This function is called each time a document needs to be changed. It is called from the changeLanguage() function.

The process is simple. If the frameNum parameter indicated that you are changing the current document, then the value of self.location is set to a new value. If you are dealing with a frame, then self.frames[frameNum].location is set to a new value.

These new values are the URLs of the document in the selected language. These are created by calling the replace() function, which searches the URL of the current document (provided by the href property of the location object) and replaces the current language with the new language.

Notice that the replacement string for the languages is indicated by referencing the fileName property of an object stored in the language array. As you will see later in this chapter when the languageObj object is discussed, values are stored for each language in the language array. The fileName property indicates how the language is indicated in a document name in a URL.

The replace() Function

This function is a simplified version of the replace() function from a set of search-and-replace functions developed in Chapter 10 of Teach Yourself JavaScript in a Week, also from Sams.net Publishing.

The principle underlying this function is simple. It relies on the substring() method of the string object to move through the target string looking for occurrences of the original term and switching them to the new term. Because you are passing a URL and two languages to the function, the function effectively changes the URL to point to the current document in the new language.

For reference, the source code for the complete set of search-and-replace functions is included here:

<SCRIPT LANGUAGE="JavaScript">
<!— HIDE FROM OTHER BROWSERS
// SET UP ARGUMENTS FOR FUNCTION CALLS
//
var caseSensitive = true;
var notCaseSensitive = false;
var wholeWords = true;
var anySubstring = false;
// SEARCH FOR A TERM IN A TARGET STRING
//
// search(targetString,searchTerm,caseSensitive,wordOrSubstring)
//
// where caseSenstive is a boolean value and wordOrSubstring is a boolean
// value and true means whole words, false means substrings
//
function search(target,term,caseSens,wordOnly) {
  var ind = 0;
  var next = 0;
  if (!caseSens) {
    term = term.toLowerCase();
    target = target.toLowerCase();
  }
  while ((ind = target.indexOf(term,next)) >= 0) {
    if (wordOnly) {
      var before = ind - 1;
      var after = ind + term.length;
      if (!(space(target.charAt(before)) && space(target.charAt(after)))) {
        next = ind + term.length;
        continue;
      }
    }
    return true;
  }
  return false;
}
// SEARCH FOR A TERM IN A TARGET STRING AND REPLACE IT
//
// replace(targetString,oldTerm,newTerm,caseSensitive,wordOrSubstring)
//
// where caseSenstive is a Boolean value and wordOrSubstring is a Boolean
// value and true means whole words, false means substrings
//
function replace(target,oldTerm,newTerm,caseSens,wordOnly) {
  var work = target;
  var ind = 0;
  var next = 0;
  if (!caseSens) {
    oldTerm = oldTerm.toLowerCase();
    work = target.toLowerCase();
  }
  while ((ind = work.indexOf(oldTerm,next)) >= 0) {
    if (wordOnly) {
      var before = ind - 1;
      var after = ind + oldTerm.length;
      if (!(space(work.charAt(before)) && space(work.charAt(after)))) {
        next = ind + oldTerm.length;
        continue;
      }
    }
    target = target.substring(0,ind) + newTerm + 
         _target.substring(ind+oldTerm.length,target.length);
    work = work.substring(0,ind) + newTerm + 
         _work.substring(ind+oldTerm.length,work.length);
    next = ind + newTerm.length;
    if (next >= work.length) { break; }
  }
  return target;
}
// CHECK IF A CHARACTER IS A WORD BREAK AND RETURN A BOOLEAN VALUE
//
function space(check) {
  var space = " .,/<>?!`';:@#$%^&*()=-|[]{}" + '"' + "\\\n\t";
  for (var i = 0; i < space.length; i++)
    if (check == space.charAt(i)) { return true; }
  if (check == "") { return true; }
  if (check == null) { return true; }
  return false;
}
// STOP HIDING —>
</SCRIPT>

This script library consists of three functions: search(), replace(), and space():

Instructions for how to correctly use search() and replace() appear in the preceding source code.

The selectLanguage() Function

The selectLanguage() function opens a dialog box with a menu in it so that the user can select a language.

When the new window is opened, the chooseDialog variable is made to point at the window and then the content is displayed in the window. The bulk of the content is generated by the buildMenu() call.

It's important to note that the header of the document being displayed in the dialog box includes a variable definition creating a pointer to the window, which opened the dialog box. This is done using the window.opener property. The reason this variable is defined is so that when the user selects a language, it is possible to call back to the main window and trigger a language change.

The buildMenu() Function

This function looks complex but is really rather simple. It takes two arguments: dialog (which specifies if the menu will be displayed in a dialog box), and complete (which specifies if the complete language list should be displayed or the complete list less the current language).

These parameters are passed so the menu that is built works in the context it will be used.

The dialog parameter is used to ensure that the call to changeLanguage() is made correctly. If the menu is in a dialog box, then changeLanguage() is a property of mainWindow; if it is not a dialog box, the call is to the parent of the current frameset.

The function itself builds a string called menu with all the HTML needed to produce a form with a selection list of all the languages and a button with an onClick event handler to call changeLanguage() as long as the user has selected a language.

The languageObj() Object Definition

The last function in the script library is the languageObj() object definition function. It is used to define the objects stored in the language array.

The function defines an object with two properties: displayName, which specifies how the name of the language should be displayed to the user, and fileName, which indicates how the language name appears in file names in URLs.

The Configuration Scripts


As indicated earlier, there are two configuration scripts: one included in the parent frameset or each document of a non-frames site, and another included in each document in a site (but not a parent frameset).

The First Configuration Script

The following script needs to be included in the parent frameset document or in each document in a non-frames site:

<SCRIPT LANGUAGE="JavaScript">
   language[0] = new languageObj("english","English");
   language[1] = new languageObj("french","French");
   language[2] = new languageObj("spanish","Spanish");
   currentLanguage = 0;
   wantWaitDialog = false;
</SCRIPT>

This set of variable assignments declares those universal variables that are used throughout. In this example, languages are assigned to the language array by creating instances of the languageObj() object. The first parameter is the language name in files, and the second is the language name displayed for the user.

The particular languages declared here could just as easily be a different number and combination of languages:

language[0] = new languageObj("english","English");
language[1] = new languageObj("french","Francais");
language[2] = new languageObj("spanish","Espanol");

or

language[0] = new languageObj("simplechinese","Simplified Chinese");
language[1] = new languageObj("tradchinese","Traditional Chinese");
language[2] = new languageObj("japanese","Japanese");
language[3] = new languageObj("korean","Korean");

After the languages are configured, the currentLanguage variable needs to be set. In a parent frameset, this would be set to the language initially displayed in the frames; in a non-frames situation, this would be set to the current document's language. The value assigned to currentLanguage should by the index of the language in the language array.

Finally, the wantWaitDialog variable takes a value of true or false depending on whether or not a dialog box with a wait message should be displayed each time a language change occurs.

The Second Configuration Script

This script is simple and is included in any displayed document (in other words, not in a parent frameset):

<SCRIPT LANGUAGE="JavaScript">
   var change = true;
</SCRIPT>

This script simply sets the change variable to true or false. This indicates if the current document should be changed when the user attempts to change languages. This can be used in a frameset to keep the menu frame or some graphical frame from changing.

Using the Scripts


There are several ways to use the scripts. The two basic methods are


Non-Frames Sites


When implementing language-switching functionality in a non-frames site, it is assumed that a language menu will appear in each document.

As previously indicated, three scripts need to be included in each document as follows:

<HEAD>
<SCRIPT LANGUAGE="JavaScript" SRC="language.js"></SCRIPT>
<SCRIPT LANGUAGE="JavaScript">
   language[0] = new languageObj("english","English");
   language[1] = new languageObj("french","French");
   language[2] = new languageObj("spanish","Spanish");
   currentLanguage = 0;
   wantWaitDialog = false;

</SCRIPT>

<SCRIPT LANGUAGE="JavaScript">
   var change = true;
</SCRIPT>
<TITLE>The English Document</TITLE>
</HEAD>

This header would be used in English documents in the site. The value assigned to currentLanguage would be 1 in French documents and 2 in Spanish documents.

The documents themselves need to include a menu. This can be done by calling buildMenu() in the body of the document:

<BODY>
<DIV ALIGN=CENTER>
<H1>The English Document</H1>
<HR>
<SCRIPT LANGUAGE="JavaScript">
document.write(buildMenu(noDialog,complete));
</SCRIPT>
</DIV>
</BODY>

This produces results similar to those shown in Figure 17.2.

Figure 17.2. The basic language-changing menu.

Notice, however, that the menu includes the current language because the constant complete was passed to buildMenu(). To ensure that the current language is not included, pass notComplete instead of complete to buildMenu(). This produces results like those in Figure 17.3.

Figure 17.3. The menu can be made to not include the current language.

There are two other ways that the menu can be built:


Using a Dialog Box

If the changeLanguage() function is called without any arguments, then a dialog box is displayed prompting users to select a language.

Using this, it is possible to change pages so that the body of the document looks like the following:

<BODY>
<DIV ALIGN=CENTER>
<H1>The English Document</H1>
<HR>
<STRONG><A HREF="javascript:changeLanguage()">Change Language</A></STRONG>
</DIV>
</BODY>

Then, when a users clicks on Change Language, a language-selection dialog box is displayed. This produces results like those in Figures 17.4 and 17.5.

Figure 17.4. A single link can be used for changing languages.

Figure 17.5. When the link is clicked, a dialog box is displayed.

This technique is useful when the list of languages is long or the menu would clutter up a document excessively.

Calling changeLanguage() Directly

Another approach to building a menu requires that a manually built menu, which directly calls changeLanguage(), be included in each document. For instance, the body of the document could look like the following:

<BODY>
<DIV ALIGN=CENTER>
<H1>The English Document</H1>
<HR>
<STRONG>
<A HREF="javascript:changeLanguage(1)">Change to French</A>
&#0149;
<A HREF="javascript:changeLanguage(2)">Change to Spanish</A>
</STRONG>
</DIV>
</BODY>

This produces a document resembling Figure 17.6.

Figure 17.6. Manually-built links can be used for language switching.

Language Switching in a Frameset


Things work a little differently in a frameset. The parent frameset needs to include the language functions script plus the first configuration script:

<HEAD>
<SCRIPT LANGUAGE="JavaScript" SRC="language.js"></SCRIPT>
<SCRIPT LANGUAGE="JavaScript">
   language[0] = new languageObj("english","English");
   language[1] = new languageObj("french","Francais");
   language[2] = new languageObj("spanish","Espanol");
   currentLanguage = 0;
   wantWaitDialog = false;
</SCRIPT>
<TITLE>Multiple Languages</TITLE>
</HEAD>
<FRAMESET COLS="50%,*">
   <FRAME SRC="menu.html">
   <FRAME SRC="text.english.html">
</FRAMESET>

Then, the various documents need to include the following script in their headers:

<SCRIPT LANGUAGE="JavaScript">
   var change = true;
</SCRIPT>

The menu file doesn't need to change when the user changes languages, so it should include the following:

<SCRIPT LANGUAGE="JavaScript">
   var change = false;
</SCRIPT>

In the menu file, whether you are using buildMenu() or changeLanguage() links to create your menu, it is important to remember that the functions are in the parent frameset. Calls to buildMenu will have to look like the following:

document.write(parent.buildMenu(parent.noDialog,parent.complete));

Similarly, calls to changeLanguage() should be called in the parent frameset.

This produces results that look like Figure 17.7.

Figure 17.7. Language changing in a frameset.

As with the preceding non-frames examples, any of the available menu methods can be used, including opening a dialog box and manually building the menu links.

Summary


In this chapter you built a set of functions that can be used to implement anything from a single-document multiple-language interface to multi-frame sites in which all, or some, of the frames change language.

In doing this, you applied several basic principles, including:

The next chapter looks at another example, and takes you through the development of a solitaire game in JavaScript that demonstrates several techniques—including interactive forms, object definition and instantiation, and the use of random numbers.

Previous Page Page Top TOC Next Page See Page