Articles Archive
Articles Search
Director Wiki

Multilingual bookmarks

March 14, 2000
by Michel Croibier

Bookmarking pages in a CDROM is a subject that has already been discussed ion Director Online, but I would like to add some ideas and tricks to get the bookmarks platform and language-independent.

We will also show how to give the bookmark term a larger signification by being able to bookmark not only movie files but also the global state the CD is in when the bookmark is created.

The basics

Multi-lingual bookmarking relies mainly on a bookmark format that is platform and language independent. For my approach, this format already exists; it is the URL format (protocol://machine/path/file). For this article, we will not use the protocol portion of the URL format.

Of course, you don't want the user to see URL. They only need to see the title of the bookmarked page. This means that this title can't be hard-coded but has to be dynamically displayed depending on the language the user chooses when starting the CD. If we can do this, the bookmark list will then become a personalized journey through the CDROM. The user will then be able to save it to disk (for a commercial presentation by example) and he can retrieve it in the language of his audience.

These two aspects of the problem (path names and language independence) will be discussed below.

Getting platform independence

The main problem when bookmarking a file is that one can get different paths names depending on the runtime platform:

In the message window type put the pathName, you get...

put the pathName
-- C:\myData\ 

on a Windows platform and...

-- Macintosh Hard Disk:myData:

...on a Mac.

Director offers an operator (the / at / sign) that allows you to include the current path name in "go to movie" calls or linked asset pathnames. This is fine but it is far too limited to handle complex paths. That's why I prefer to put all my files into sub-folders of a unique "root" directory.

My stub-projector resides outside of this folder and the only thing it does is a "go to movie" call to a movie called index.dir inside the root folder.

In the second frame (I don't like first frames) of the stub-projector one can find (assuming my root directory is called "root"):

on exitFrame
  global gSeparator
  if the machineType=256 then
    --it's a PC
  end if
  go to movie the pathName & "root" & gSeparator & "index"

In the index.dir file one can find in the usual "on init" handler:

on init
  global gRootLevel
  gRootLevel=the pathName

Now, any reference to a movie path we will use will rely on this global gRootLevel. To make things easier, we will use a unix-like syntax where the first / means "the root level".

By example if the movie movieA.dir needs to go to movieB.dir we will use:


The MyGoTo handler will do the job of resolving the unix path into a platform specific absolute file path and will then call a standard "go to movie":

The resolved pathname...

MyCDROM Name:myData:Bmovies:movieB

... (on Mac) where MyCDROM Name:myData: is taken from gRootLevel, Bmovies:movieB is taken from the parameter and from gSeparator.

The process of resolving unix file paths is done by the resolve handler:

This example uses the older syntax in Director 6.5.
Variables names have been translated from the French.

global gSeparator,gRootLevel
on resolve thePath
set firstChar=chars(thePath,1,1)
case firstChar of
    -- it's an absolute path
    set thePath = gRootLevel & chars(thePath, 2, length(thePath))
    -- It's probably the upper level
    -- We suppose that thePath starts with "../"
    -- Getting the absolute path of level-1
    set absolute=the pathname
    set absoluteM1="" -- Level Minus 1
    set the itemDelimiter=gSeparator
    set levels=(the number of items in absolute) -1 
    -- "-1" because absolute ends with a separator.
    if levels=1 then -
      -- We can't go up
      alert "Error in Resolve :"&RETURN&QUOTE&".."&QUOTE&" forbidden"
    end if
    -- Assembling level -1
    repeat with i=1 to levels-1
      set absoluteM1=absoluteM1 & (item i of absolute) & gSeparator
    end repeat
    -- replace "../" in thePath by  absoluteM1
    set thePath = chars (ThePath,4,length(thePath))
    set thePath = absoluteM1 & thePath
    -- It's a simple relative path
    set thePath=the pathname & thePath
end case
-- Here we have an absolute path containing 
-- "/" or ":" (mac) or "\"(Win)
-- We replace remaining "/" by gSeparator.
set thePath2=""
repeat with i = 1 to length(thePath)
  set currentChar=chars(thePath, i, i)
  if currentChar="/" then
    set currentChar=gSeparator
  end if
  set thePath2=thePath2& currentChar
end repeat
return thePath2

Because "/" is used as a separator you cannot use it in your folder or file names. Now that we can access the files in platform-independent way, we will now take care of the title to be displayed.


The main bookmarking process has already been discussed so I will make this explanation short.

Bookmarking is storing something that tells the program "where the user is". The definition of " where the user is" is: The place the user last went to. Because any movement in the CD is done with the myGoto handler, the only thing to do is to store the argument passed to myGoto in a global. When the user clicks on "add bookmark" we simply append this value to a global list of bookmarks.

on myGoto arg
  global gMyGotoLastParam
  set gMyGotoLastParam=arg
  go to movie(resolve(arg))
on addBookmark
 global gMyGotoLastParam , gBookmarkList
 append(gBookmarkList, gMyGotoLastParam )

Retrieving a bookmark is trivial, displaying is not. This will be discussed later.

Providing back and forward buttons

If you want to handle a history list (just like a browser's back and forward buttons), it's simple. Note that we use here a global list to store the bookmarks and a pointer to mark where we currently are in this list.

on myGoto arg
on jump arg
  global gMyGotoLastParam
  set gMyGotoLastParam=arg
  go to movie (resolve(arg))
on addToHistory arg
  global gHistoryList,gHistoryPtr
  set listTemp=[]
  repeat with i=1 to gHistoryPtr
    append listTemp, getAt(gHistoryList, i)
  end repeat
  set gHistoryList=duplicate(listTemp)
  -- free some ram
  set listTemp=0
  append gHistoryList, arg
  set gHistoryPtr=count(gHistoryList)
on historyBack
  global gHistoryList,gHistoryPtr
  if gHistoryPtr>1 then
    set gHistoryPtr=gHistoryPtr-1
  end if
on historyForward
  global gHistoryList,gHistoryPtr
  if gHistoryPtr< count(gHistoryList) then
    set gHistoryPtr=gHistoryPtr+1
  end if

Getting language independent

Let's start with the simpler aspects of the providing language independence.

Our bookmarks (the pathnames) are obviously language independent (within the limits of using a roman encoding system: I have not tried this on a Japanese system). Because we rely on the use of the gRootLevel global, we can easily swap languages on the fly by hacking this global and reloading the bookmark.

Here is an example of the folder structure of the CD.

In this example the root folder is called data. You can find the projector schneider. The codebase.cst contains all the scripts needed by all the movies (note that it should not contain language-specific cast members). The commun folders contains castlibs that store the language independent media (music, icons, graphics...). The two folders LOC and "US" contain respectively the localized and US versions of the movies.

Obviously, gRootLevel will be (on Mac): CDROM:A:LOC: when using the localized version. A small handler that takes off the last item in gRootLevel replacing it by US and calls something like jump(gMyGotoLastParam) will swap the language on the fly.

Displaying the bookmarks

Because the user doesn't care about the file path (or URL) of the page they bookmarked, we have to display the titles of the pages. These titles are always in a particular language and can't be stored within the bookmark list.

We have to create a table for the URLs and the corresponding NameOfPageDisplayed. Only this table has to be translated when localizing the CD. This table can be stored as a TAB-delimited text field and is a member of a language specific castlib (ie INSIDE the US or LOC folders). The storage in a field rather than in an external text file was chosen because when the movie plays on Windows, the fontmap mechanism in Director converts the ASCII codes from Mac to Windows. This is needed for languages that use accents on small letters such "éàèûï".

Because we would like to be able to bookmark any part of a CD (even inside a director movie, or in a MIAW), we must think in terms of a URL that represents the state the CD is in when "add bookmark" is clicked", not just the active movie.

Because we can't predict what the author's view of a state is, we must use something resembling a browser cookie (a string) that is passed by the stage to the bookmark engine when the bookmark is created. When the bookmark is called, the engine must give back the cookie to the stage. The stage will then be told to run an processCookie handler by the engine.

If the processCookie handler is in an internal castLib of the stage, each movie can generate any string for a cookie (loc of sprites, variables, marker, etc).

I called the string a cookie because you can imagine that the stage is a Web server sending a cookie to the browser. The browser (the bookmarking process) doesn't try to understand what is in the cookie, it will just store it. When the user gets back to the URL, the browser will just send back the cookie to the server. It's the responsibility of the server (the stage in this case) to correctly interpret the cookie via his own processCookie handler. That's why this handler must be in the internal castlib of the stage.

The last thing we have to do is integrate the cookie in the URL:






Of course, it's the responsibility of the stage to send a non-localized cookie, or to include in the cookie string the language the cookie is valid in.

How does the stage send the cookie?

Because the myGoto handler is only informed of changes in the current state when he is called, we must use a global gCurrentCookie.

This global is modified or set by the stage when it's state has changed (not only when a go to movie is needed). The bookmark engine can read this value and append it to gMyGotoLastParam in the bookmark when it is called.

The history engine has another challenge: It must be aware of the change of state in order to be able to go back to the previous state but it is not expressly called. The only way to check for a change of state is to check gMyGotoLastParam and gStageLastCookie in an idle loop. If a change occurs in gStageLastCookie, then the old state must be appended to the history list and gMyGotoLastParam must be updated.

In the idle handler one can find:

set the itemDelimiter="?"
if item 2 of gMyGotoLastParam<>gStageLastCookie then
  set gMyGotoLastParam =item 1 of gMyGotoLastParam&"?"&gStageLastCookie
end if

The table of bookmark URLs and the NameOfPageDisplayed

Because of the complexity of the URL, the bookmark-displaying engine will try to find the nearest URL in the table that corresponds to the actually bookmarked URL. Then it displays the NameOfPageDisplayed that corresponds to this nearest URL. (The process of quickly finding a record in a table is beyond the scope of article.)

The precision of the bookmark-displaying engine depends greatly of the granularity of the table. Note that this granularity does not need to be constant through the table (some chapters can be roughly bookmarked and some others can be accessed with the finest precision).

Here is an example of a short bookmarkList:

"/x00000/000000.dir?nrj ","/x00000/000000.dir?indus",

The last bookmark indicates that a MIAW was opened when the bookmarking was done. The MIAW contains a file: /fic/p77.dir (the ".dir" is omitted).

And how it looks like for the user:

In English and

In French. Note that the two buttons "load" and "save" were not localizable because this little MIAW (containing the bookmarking engine) must survive the language switching. The markets bookmark was refreshed in French as Domaines.

Here are the table entries:

In French:

Domaines   /x00000/000000.dir?start
Bâtiment  /x00000/000000.dir?bat
Energie /x00000/000000.dir?nrj
Industrie       /x00000/000000.dir?indus
Infrastructures /x00000/000000.dir?infra
K-System        /fic/p77.dir 

In English:

Markets    /x00000/000000.dir?start
Construction    /x00000/000000.dir?bat
Electric power  /x00000/000000.dir?nrj
Industry        /x00000/000000.dir?indus
Infrastructure  /x00000/000000.dir?infra
K-System        /fic/p77.dir 

This process has a drawback

If you want the title displayed in the bookmarks to represent a certain cookie, you will have to include a record in the table that reflects this cookie. That's why the table can get very big!

Sometimes it is not possible to have all the states in the table. For example, suppose we have 100 movies that can call a miaw chosen in a set of 100 miaws. Then you've got 100*100 = 10,000 combinations!

In the example above, you can note a special URL:


This bookmark is made of 2 pathnames (one in the URL itself and one in the cookie).

You can note that in the table we have:

K-System   /fic/p77.dir

Somewhere else in the table we can find.

Houses     /x00000/200000/240000/242000/242010/

You can't find in the table the full bookmarked URL.

This is a case where the bookmarking engine has to understand the cookie! The author, in this case, asked me to use the name of the miaw (K-System) to display the bookmark but to "remember" the complete state (stage and Miaw). Of course it's simple to display a combinated name (House / K-System) if one wants to.


This process of knowing at any time the state the CD is in provides the capability of precisely bookmarking and the opportunity to handle a history of the journey through the CD.

This journey can be replayed in any language and on any platform.

As long as you don't have to handle combination effects, the cookie process is relatively pure (no requirement for the bookmarking engine to be able to "understand" the cookie).

Further explorations

An author asked me recently to add the possibility for the user to import his own images (BMP or PICT files) in his own journey. The bookmarking engine permits you to put the image's filename in the cookie... but where can I put the image's file? Because I want the journey file to stay unique (no folders to copy), I had to add the protocol part of the URL and handle some different types of bookmarks :


I stored the images in an external castlib that contains also the string version of the bookmark list. So I got only one journey file.

When the user recalls a Image:// bookmark I call a specific movie (imageDisplay.dir) and pass the image's member name in a global. The imageDisplay.dir performs in second frame:

on prepareFrame
  global gImageToDisplay
  sprite(10).member= member(gImageToDisplay)

The name displayed in the bookmark miaw is then the file name of the image. The process stays pure because no cookie parsing has to be done (no combinated miaws in this case).

Michel Croibier started working for TV live shows in France, using Director 4.0 and DTMF telephone tones to develop interactivity between the studio and the people watching the show in their houses. Today, with two offices in Grenoble-France and Lausanne-Switzerland, he is working on the newest concepts in large companies communication.

Copyright 1997-2019, Director Online. Article content copyright by respective authors.