Mise à jour du 24/07/10. — Le code de YALT a été réécrit de A à Z. Le présent article ne reflète pas cette évolution. YALT 2 produit désormais l'objet L10N de façon beaucoup plus compact, il améliore la compatibilité avec des scripts de portée session (#targetengine session) et permet au développeur d'échapper les caractères Unicode via la syntaxe \u.... (afin de conserver tout le script en encodage Ascii). Vous pouvez également consulter la locale active en interrogeant la propriété L10N.locale (qui retourne une chaîne de caractères). Enfin, YALT 2.1 abandonne totalement la technique du « fichier qui se lit lui-même » et offre la possibilité de composer avec des paramètres additionnels via des arguments formels %1, %2, etc.
Accéder à l'article actualisé.

BILLET ARCHIVÉ :

Le processus de localisation (L10N, en abrégé) consiste à traduire et afficher l'interface d'un logiciel dans la langue la mieux adaptée à l'utilisateur final (idéalement, sa propre langue). Typiquement, on attend d'une application localisée qu'elle fournisse des menus, des boîtes de dialogue, des messages d'avertissement dans la langue cible.

Du point de vue du scripting, il est plus facile d'administrer un fichier unique que de distribuer les multiples versions traduites du même script. Idéalement, on aimerait coder toute l'interface dans une langue par défaut (disons, l'anglais international) et pouvoir attacher ensuite un fichier de traduction pour chaque langue. Un tel mécanisme a été popularisé avec l'avènement du “format PO” introduit par le système de traduction Gettext (GNU). Il est employé ou imité dans de nombreuses bibliothèques de scripts, depuis PHP jusqu'à JavaScript.

Toutefois, dans le cadre du JavaScript Indesign, et afin de contourner les difficultés de l'inclusion de fichiers, nous préférons travailler sur un fichier unique qui rassemblera le code principal et les données de localisation.

Deux mots sur la localisation ExtendScript

ExtendScript propose un mécanisme de localisation immédiatement disponible dans vos scripts via les « objets localisés » (localization objects). Il s'agit d'objets JavaScript déclarés littéralement, dont les noms de propriétés coïncident avec les codes de langue standard et décrivant pour valeurs les chaînes de caractères localisées. Un code de langue prend la forme LL_RR (par exemple: en_US, fr_FR...), où LL est un identifiant de langue ISO 639 et RR un identifiant de région ISO 3166.

Lorsque vous chargez à true la propriété localize de l'objet Dollar ($), vous activez le mécanisme de localisation automatique dans la méthode toString. Vous pouvez alors employer une syntaxe du genre :

// activer la L10N automatique
$.localize = true;
 
// créer un objet localisé en 2 langues
var locMsg = {
    en_US: "Hello, world!",
    fr_FR: "Bonjour tout le monde !"
    };
 
// afficher le message localisé
// [ appel implicite à toString() ]
alert( locMsg );
 

Comme expliqué dans le JavaScript Tools Guide d'Adobe, ExtendScript fait alors correspondre la localisation et la plate-forme utilisateur avec la propriété adéquate de l'objet localisé et transmet les traductions correspondantes. Sur un système français, par exemple, la propriété fr_FR: "Bonjour tout le monde !" sera convertie en "Bonjour tout le monde !"

Bref, l'approche ExtendScript est très intuitive. Toutefois, elle requiert la création d'un objet à chaque fois qu'on l'on souhaite afficher un message à l'écran. Imaginez que votre script offre une interface luxuriante truffée de titres de boutons et de contrôles! Vous supposez sans doute qu'il sera aisé de centraliser l'ensemble des objets L10N dans un tableau global (array) puis de transmettre les requêtes localisées à ce traducteur magique. Eh bien non, ce ne sera pas si simple ! Comment déterminer et adresser les clés d'un tel tableau, sinon en créant autant de nouveau mnémoniques ?

Réinvention de la roue

Dans les premiers scripts français-anglais que j'ai écrits pour InDesign (comme IndexBrutal ou EanDesign), j'ai pris l'habitude d'introduire une section « LINGUISTICS » contenant un gros paquet de variables globales dédiées à la localisation. Chaque entrée de cet ensemble était structurée comme un tableau de deux chaînes de caractères, le texte anglais d'une part, sa traduction française de l'autre. Par exemple :

var ERR_FILE_OPENING =
    [
    "Unable to open the file",
    "Impossible d'ouvrir le fichier"
    ];
 

Puis je déclarais une variable LANG ainsi définie :

var LANG = (app.locale == Locale.frenchLocale) ? 1 : 0;
 

Du coup, le script pouvait accéder au message approprié au contexte linguistique en utilisant la syntaxe ERR_FILE_OPENING[LANG].

Mais je n'ai pas tardé à prendre conscience que cette méthode faussement astucieuse revenait finalement à refaire avec des Array ce que ExtendScript fait avec des Object, sans réduire sensiblement la lourdeur de syntaxe ni abolir les maudits mnémoniques.

L'approche « YALT » (« Yet Another Localization Technique »)

J'aimerais à présent aborder le cadre de la localisation sous un nouvel angle. L'idée de base consiste à utiliser la syntaxe des commentaires JS pour stocker l'ensemble les chaînes localisées en un seul bloc au début du script. Comme ceci :

//======================================
// <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE
//======================================
// Yes :: Oui :: Ja
// I love :: J'aime :: Ich liebe
// This is a translated text :: Ceci est un texte traduit :: Dies ist ein Text in Übersetzung
// </L10N> ::
 

Du fait que toutes les lignes débutent par un double slash (//), le code ci-dessus est parfaitement silencieux et inoffensif. JavaScript ne peut pas l'interpréter, nous si ! Il s'agit évidemment d'une table de traduction. La première ligne fournit l'en-tête générique du bloc de localisation, indiquant les langues disponibles :

//======================================
// <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE
//======================================
 

La syntaxe " :: " (espace + 2 fois deux-points + espace) n'est qu'un séparateur arbitraire. Le premier champ (<L10N>) possède une signification particulière : il agit comme une balise d'ouverture pour la procédure de localisation. Vous pouvez aussi le regarder comme un marque-place matérialisant la première colonne de la table de traduction. Cette première colonne correspond à la localisation/langue par défaut. Celle-ci est adoptée quand celle de l'utilisateur n'est pas prise en charge ou quand une chaîne particulière ne possède pas de traduction dans la langue désirée. Ceci explique pourquoi la première colonne contient les messages en anglais (langue par défaut), lesquels messages seront considérés comme les clés d'accès de la table :

// Yes :: Oui :: Ja
// I love :: J'aime :: Ich liebe
// etc.
 

La première ligne ci-dessus indique que "Yes" est un message-clé par défaut susceptible d'être traduit par "Oui" en contexte français, "Ja" en contexte allemand. Dans tout autre contexte, la chaîne "Yes" sera prise pour elle-même, à moins que vous n'ajoutiez par la suite une nouvelle colonne (par exemple : " :: SPANISH_LOCALE") et les traductions associées. Chaque nouvelle ligne de la table introduit de la même façon un message-clé et les chaînes localisées correspondantes, jusqu'à ce qu'on atteigne la balise de fin </L10N> ::

Tout cela est bien joli, mais comment cette syntaxe virtuelle va-t-elle pouvoir fonctionner dans un script réel ?

Partie 2