March 96 - Country Stringing: Localized Strings for the Newton
Country Stringing: Localized Strings for the
Maurice Sharp
Newton products are currently available localized for English, French, German, and
Swedish. Thus, to take full advantage of the market, Newton applications must be
developed for four languages. As of Newton Toolkit version 1.5, there's a mechanism
for localizing strings at compile time but no built-in support for organizing all the
categories of strings across the different languages (unlike on the Macintosh, where
you can use resources). This article presents a couple of ways to organize localized
strings in your Newton application.
Until Newton Toolkit 1.5, developing an application for English, French, German, and
Swedish required four different application projects or many skanky contortions. This
was tedious, to say the least, but necessary for those who wanted to take full advantage
of the worldwide market for Newton products.
Newton Toolkit 1.5 provides support (with the SetLocalizationFrame and LocObj calls)
for localizing your applications from just one project. But this is useful only at
compile time, and it doesn't provide an infrastructure for organizing and categorizing
the localized objects. In other words, you can have different strings for four locales,
but how you keep track of what strings you have and which ones need localizing is up to
you. Macintosh developers don't have this problem because all strings can reside in
resources; changing the strings in the resources changes them in the application.
This article presents two ways to organize your localized strings. Both methods are
meant to be used at compile time, but there's also information on changing strings at
run time. Before reading this article, you should be familiar with the information in
the Newton Programmer's Guide on localizing Newton applications.
STRINGING YOU ALONG WITHOUT RESOURCES
In a Macintosh application you can keep localized strings in the 'STR#' resource of the
resource fork. This isn't an option in a Newton application for two reasons: ResEdit
doesn't directly support Unicode strings, and, more important, a Newton application
doesn't have a resource fork. All your strings have to reside somewhere in your
application package.
A first cut at a solution to the problem of how to organize localized strings in your
Newton application would be to have a viewSetupFormScript or TextSetup method
(where applicable) that sets a particular string based on some application-global
setting. This solution has several disadvantages, such as spreading localized strings
throughout the code (resulting in multiple copies of strings) and requiring all strings
for all countries to be included.
If you've programmed the Newton for a while, you might think of taking advantage of
dead code stripping and using an if statement that switches on a compile-time constant.
This would eliminate unused localized strings but is still awkward.
The best idea is a technique that lets you keep all your strings together. You can do this
by defining a frame in your Project Data with one slot per string that you want to
localize. You can even use nested frames. For example:
constant kUSStrings := '{
AppName: "World Ready!",
ExtrasName: "World!",
HelloWorld: "Hello World",
Cancel: "Cancel",
Yes: "Yes",
No: "No",
},
constant kFrenchStrings := ...
In Newton Toolkit 1.5 and later, you can use this frame with SetLocalizationFrame.
Unfortunately, there's no specification for how to build up the frame, which is
essential to organizing your strings in a sane way. Also, SetLocalizationFrame is meant
only for compile-time localizations. With some extra effort you can organize the
strings in a way that allows them to be localized at run time as well. As the next
section shows, the key is using the Load command in combination with a few constant
functions.
LINGUA FRAMA -- CREATING THE LANGUAGES FRAME
In the previous section, we defined a frame that can be used for each target language.
Each of those target language frames can be nested into an outer frame, called the
languages frame. Each target language subframe contains the localized strings in that
language. These subframes can in turn contain other subframes, enabling you to group
strings into logical categories such as strings used in filing, strings used in searching,
and so on. Each of the frames at the top level of the languages frame must have the same
structure. If you have a path in the USEnglish frame of Entries.Names.Phones.Home,
that path will also need to exist in French, German, and any other languages your
application supports.
The overall structure of the languages frame is as follows:
AppName: "World Ready!",
Cancel: "Cancel",
OK: "OK",
// ... and so on
},
AppName: "Prêt pour le Monde!",
Cancel: "Annuler",
// ... and so on
},
AppName: "Welt Ready!",
Cancel: "Absagen",
// ... and so on
},
// ... and so on
}
This is the format of the frame you would pass to SetLocalizationFrame as well as of a
constant that can be used in runtime localization. Typically, the languages frame would
be kept in a text file or in your Project Data. The problem with this is that the frame
is rather large, and adding or changing an entry in a language subframe can be
difficult. Also, several entries are identical (such as the string for OK).
A better solution is to separate the localized strings by category. This article uses the
target languages as the categories, though you could also employ similar techniques
with other categories. Once the strings are split, you can use the Load command to
assemble the languages frame.
There are two main schemes for organizing the strings. One uses simple text files and
works on both the Mac OS and Windows platforms. The other uses compile-time
functions to read the strings from some other format; on the Macintosh platform, this
method can be used to construct the languages frame from a resource file. We'll look at
each of these methods in turn.
LOADING FROM TEXT FILES
In the first scheme, you separate each language into a different text file. Remember
that Load will return the result of the last statement it executes in the specified file.
This means that each text file will specify one frame. For example, the contents of
your French text file might look like this:
AppName: "Prêt pour le Monde!",
Cancel: "Annuler",
// ... and so on
}
You could then modify your Project Data to build the localization frame:
SetLocalizationFrame({French: Load(HOME & "FrenchStrings.f"), ...
It's also helpful to have some string constants that can be used in multiple places. A
good example is the string for OK, which is the same in some languages. To do this, you
should load some general constants before constructing the individual languages that
make up the languages frame. So the overall process for building the languages frame
would be as follows:
• Load a file of string constants.
• Construct an empty languages frame.
• For each language, build the individual target language frame and add it to
the languages frame.
You only need predefined constants if you aren't using object
combination. Object combination, a feature that exists as of Newton Toolkit
version 1.6, would solve the problem of multiple instances of a single string
(such as "OK").*
The above description smells of an algorithm. Since you can run NewtonScript at
compile time, you can call a function to load a languages frame from text files (see
Listing 1). The main trick of this function is that it uses the language symbol to create
a pathname for Load.
Listing 1. CreateLanguagesFrameFromText
global CreateLanguagesFrameFromText(GlobalsFilePath,
LanguagesSymArray)
begin
if GlobalsFilePath then
Load(GlobalsFilePath);
foreach sym in LanguagesSymArray do
langFrame.(sym) := Load(HOME & sym & "Strings.f");
langFrame;
end;
You can define this function in a text file (say, WorldStrings.f) that you add to your
project. Note that you must compile this file before you load your international