Update 07/24/10. — The YALT code has been completely rewritten. The following article doesn't reflect this evolution. YALT 2 encapsulates a L10N object in a more compact way, it improves the compatibility with session targetted scripts and allows the developer to escape Unicode characters with the \u.... syntax (in order to keep the entire script ascii encoded). You also can get the active locale in a string by using L10N.locale. Finally, YALT 2.1 abandons the file auto-parsing approach and provides the ability to pass string arguments through %1, %2... placeholders.
Check out the updated article.

ARCHIVED ARTICLE:

Our special format is encoded by JavaScript double slashed comments, like this:

//======================================
// <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE
//======================================
// Yes :: Oui :: Ja
// I love :: J'aime :: Ich liebe
// </L10N> ::
 

Imagine a plain text file F containing such a localization table. From a script S, it is not difficult to open F, extract the significative contents and return the data in an associative array.

L10N Syntax Parser

First, we need to capture the commented lines beetween the <L10N> and the </L10N> markers, then to parse the strings separated by ' :: '.

__sep = ' :: ';
__beg = ' <L10N>';
__end = ' </L10N>';
 
/*arr*/File.prototype.getCommentsContaining = function(/*str*/sep_)
//--------------------------------------
// Open this file and extract the lines
// beginning with '//' AND containing the string <sep_>
    {
    var r = [];
    if ( this.open('r') )
        {
        var line;
        while(!this.eof)
            {
            line = this.readln();
            if ( ( line.substr(0,2) == '//' )
                && ( line.indexOf(sep_) >= 0 ) )
                r.push(line.substr(2));
            }
        this.close();
        }
    return(r);
    }
 
/*arr*/Array.prototype.parseL10N = function(/*str*/locale_)
//--------------------------------------
// Parse this array of L10N entry lines and return
// an associative table for a specific <locale_>
// <locale_> is given in the form of a Locale enum. name:
// "ENGLISH_LOCALE", "FRENCH_LOCALE", etc.
    {
    var r = [];
    var sz = this.length;
    var lm, ss;
    var st = -1
    var rg = 0;
    for ( var i=0 ; i < sz ; i++ )
        {
        ss = this[i].split(__sep, lm);
        if ( ( st == -1 ) && ( ss[0] == __beg ) )
            {
            lm = ss.length;
            for ( var j = 1 ; j < lm ; j++ )
                if ( ss[j] == locale_ ) rg = j;
            st = 0;
            continue;
            }
        if ( st == 0 )
            {
            if ( ( rg == 0 ) || ( ss[0] == __end ) ) break;
            if ( ss.length <= rg ) continue;
            r[ss[0].substr(1)] = ss[rg];
            }
        }
    return(r);
    }
 
 
// sample code
 
var F = File("l10n_strings.txt");
var lines = F.getCommentsContaining(__sep);
var frStrings = lines.parseL10N("FRENCH_LOCALE");
 
// now, <frStrings> contains an associative array of
// French translated strings from the file "l10n_strings.txt"
alert( frStrings["Yes"] ); // "Oui"
 

The method File.getCommentsContaining(<sep>) gets the contents from the target file, and Array.parseL10N(<locale>) extracts the associations between a default locale key (like "Yes") and the corresponding string in a specific language/locale.

Friendly Locale Names

At this point, we own a generic reusable code parsing any text file encoded in the <L10N>-syntax. Keep in mind that this process doesn't return the whole localization table: it keeps only the column corresponding to a given locale. The argument we will use in the future is the app.locale name (the user locale). Since the property app.locale returns an id (Number) within the Locale enumeration class, we need to convert this number into a friendly name —for example, "FRENCH_LOCALE"— as required by our syntax. To do that, we provide a toLocaleName method to the Number object:

/*str*/Number.prototype.toLocaleName = function()
//--------------------------------------
// return the InDesign Locale friendly
// name from this Locale id
{
for ( var p in Locale )
    if ( Locale[p] == this ) return(p);
return(null);
}
 
// test:
alert( app.locale.toLocaleName );
 

For your information, here are the InDesign DOM Locale names from version 3 (CS) to 6 (CS4):

Locale Name Locale ID InDesign
ARABIC_LOCALE 0x4C434172 5, 6
CZECH_LOCALE 0x4C43437A 5, 6
DANISH_LOCALE 0x4C43446E 3, 4, 5, 6
ENGLISH_LOCALE 0x4C43456E 3, 4, 5, 6
FINNISH_LOCALE 0x4C43466E 3, 4, 5, 6
FRENCH_LOCALE 0x4C434672 3, 4, 5, 6
GERMAN_LOCALE 0x4C43476D 3, 4, 5, 6
GREEK_LOCALE 0x4C434772 5, 6
HEBREW_LOCALE 0x4C434862 5, 6
HUNGARIAN_LOCALE 0x4C434875 5, 6
INTERNATIONAL_ENGLISH_LOCALE 0x4C434569 3, 4, 5, 6
ITALIAN_LOCALE 0x4C434974 3, 4, 5, 6
JAPANESE_LOCALE 0x4C434A70 3, 4, 5, 6
KOREAN_LOCALE 0x4C434B6F 6
POLISH_LOCALE 0x4C43506C 5, 6
PORTUGUESE_LOCALE 0x4C435067 3, 4, 5, 6
ROMANIAN_LOCALE 0x4C43526F 5, 6
RUSSIAN_LOCALE 0x4C435275 5, 6
SIMPLIFIED_CHINESE_LOCALE 0x4C43436E 6
SPANISH_LOCALE 0x4C435370 3, 4, 5, 6
SWEDISH_LOCALE 0x4C435377 3, 4, 5, 6
TRADITIONAL_CHINESE_LOCALE 0x4C435477 6
TURKISH_LOCALE 0x4C435472 5, 6
UKRAINIAN_LOCALE 0x4C43556B 5, 6

Auto-parsing

The last step of our strategy consists in using the same file for the localization and for the main process. That is the keypoint of the YALT technology: any localized project remains in one file, containing both the L10N table and the specific script code. We just need to open the script File from the script itself, and to parse the commented lines as mentioned previously.

We will perform this whole task in one instruction, storing the L10N array in a static property of the String object:

String.L10N = File(app.activeScript).
    getCommentsContaining(__sep).
    parseL10N(app.locale.toLocaleName());
 

Once it is created, the array String.L10N allows to convert any registered string from the default locale into the user locale. We handle this operation through a function named __ (double underscore):

function __(s)
{
return(String.L10N[s]||s);
}
 

so that, everywhere in the script, a code such as __("Yes") will return the localized corresponding message if it exists, or the string "Yes" itself if not. This way, the developer can build quickly and genericly the whole interface of a script without worrying about translation, using the syntax __(<myDefaultLocaleString>). To localize <myDefaultLocaleString> later, you just need to write the corresponding fields in the <L10N> table.

Finally, we want the YALT toolbox to be loaded once —at the very beginning of the script— and to allow the script to run in a persistent engine. A solution is to encapsulate the code inside an if-block based on typeof(__).

Here is the resulting structure of any project:

 
// 1. Create your own localized strings by setting
//    the lines below (keep the commented syntax):
 
//======================================
// <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE
//======================================
// Yes :: Oui :: Ja
// I love :: J'aime :: Ich liebe
// </L10N> ::
 
 
// 2. Add the whole block below in your script:
 
if ( typeof __ == 'undefined' )
{
__sep = ' :: ';
__beg = ' <L10N>';
__end = ' </L10N>';
 
/*arr*/File.prototype.getCommentsContaining = function(/*str*/k_)
//--------------------------------------
    {
    // code of File.getCommentsContaining 
    }
 
/*arr*/Array.prototype.parseL10N = function(/*str*/locale_)
//--------------------------------------
    {
    // code of Array.parseL10N 
    }
 
/*str*/Number.prototype.toLocaleName = function()
//--------------------------------------
  {
    // code of Number.toLocaleName 
  }
 
String.L10N = File(app.activeScript).
    getCommentsContaining(__sep).
    parseL10N(app.locale.toLocaleName());
 
function __(s){return(String.L10N[s]||s);}
}
 
 
// 3. Use the syntax __(<string>) anywhere you need
//    to localize your text:
 
alert( __("I love") + " Indiscripts.com!");
 
 

The attached YaltSample.js file provides the complete generic code from which to build your own project.

Part 1