YALT : une autre approche de la « localisation » (2/2)
August 17, 2009 | YALT | fr | en
Dans l'épisode précédent, nous avons imaginé une syntaxe balisée ultra-légère permettant de spécifier des textes localisés au sein de nos projets JavaScript. Il reste maintenant à voir comment accéder et traiter simplement ces éléments.
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É :
Notre syntaxe est encodée sous la forme de commentaires JavaScript introduits par des doubles slashes :
//====================================== // <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE //====================================== // Yes :: Oui :: Ja // I love :: J'aime :: Ich liebe // </L10N> ::
Supposez qu'un fichier texte F contienne une table de localisation formatée comme ci-dessus. À partir d'un script S, il serait aisé d'ouvrir F, d'extraire le contenu utile et de retourner les données dans un tableau associatif :
Analyseur de syntaxe L10N
Tout d'abord, nous devons capturer les lignes de commentaires situées entre les balises <L10N>
et </L10N>
, puis analyser les chaînes séparées par ' :: '
.
__sep = ' :: '; __beg = ' <L10N>'; __end = ' </L10N>'; /*arr*/File.prototype.getCommentsContaining = function(/*str*/sep_) //-------------------------------------- // Ouvrir ce fichier et extraire les lignes amorcees // par '//' ET contenant la chaine <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_) //-------------------------------------- // Analyse ce tableau d'entrees L10N et retourne // un tableau associatif pour une <locale_> spécifique // <locale_> est fourni sous la forme d'un nom // d'enum. Locale: "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); } // echantillon de code var F = File("l10n_strings.txt"); var lines = F.getCommentsContaining(__sep); var frStrings = lines.parseL10N("FRENCH_LOCALE"); // ici, <frStrings> contient un tableau associatif des // traductions françaises extraites du fichier "l10n_strings.txt" alert( frStrings["Yes"] ); // "Oui"
La méthode File.getCommentsContaining(<sep>)
récupère le contenu utile du fichier cible, puis Array.parseL10N(<locale>)
extrait les correspondances entre une clé donnée dans la langue par défaut (telle que "Yes") et la chaîne associée dans la langue cible.
Rendre les noms de « locale » accessibles
À ce point, nous disposons d'un code générique réutilisable capable d'analyser n'importe quel fichier encodé dans notre syntaxe <L10N>
. Gardez à l'esprit que cette procédure ne renvoie pas la table de localisation complète, mais seulement la colonne correspondant à la langue recherchée. Le paramètre que nous utiliserons dans le futur est le nom de l'identifiant app.locale
(c'est-à-dire, la langue de l'utilisateur). Comme la propriété app.locale
renvoie un id (Number
) à l'intérieur de la classe d'énumération Locale
, nous devons extraire de ce nombre son nom accessible (tel que "FRENCH_LOCALE"), ainsi que le réclame notre syntaxe. Pour ce faire, nous greffons une méthode toLocaleName
à l'objet Number
:
/*str*/Number.prototype.toLocaleName = function() //-------------------------------------- // renvoie le nom accessible de cette // Locale (connue par son id) { for ( var p in Locale ) if ( Locale[p] == this ) return(p); return(null); } // test: alert( app.locale.toLocaleName );
Pour information, voici la liste des Locale InDesign de la version 3 (CS) à la version 6 (CS4) :
Nom de 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 |
Étape d'auto-analyse
L'ultime étape de notre technique consiste à utiliser le même fichier, et pour la localisation, et pour le processus principal. Telle est l'idée-clé de la « technologie YALT » : le projet localisé réside dans un fichier unique contenant à la fois la table L10N et le script. Il nous reste alors à ouvrir le fichier du script à partir du script lui-même afin d'analyser les lignes commentées selon la syntaxe définie plus haut.
Nous pouvons à présent assurer cette opération en une seule instruction, stockant les données utiles de localisation dans une propriété statique (.L10N
) de l'objet String
:
String.L10N = File(app.activeScript). getCommentsContaining(__sep). parseL10N(app.locale.toLocaleName());
Une fois créé, le tableau String.L10N
permet de convertir toute chaîne prise en charge de la langue par défaut vers la langue cible. Nous gérons cette traduction à travers une fonction nommée __
(double underscore) :
function __(s) { return(String.L10N[s]||s); }
ainsi, en tout point du script, un code du genre __("Yes")
renverra le message localisé correspondant s'il existe, sinon la chaîne "Yes"
inchangée. De cette façon, le développeur peut construire rapidement et de façon générique l'interface complète de son script sans se préoccuper de traduction, en utilisant la syntaxe __(<myDefaultLocaleString>)
. Pour localiser ensuite <myDefaultLocaleString>
, il vous suffira de saisir les champs correspondants dans la table <L10N>
.
Nous souhaitons enfin que le mécanisme YALT soit chargé une fois au tout début du script, sans être compromis par d'éventuelles conditions de persistance induites par la directive #targetengine
. Une solution consiste à encapsuler tout le code dans un bloc if
testant l'existence de la fonction __
par typeof(__)
.
Voici donc la structure résultante d'un projet :
// 1. Créez vos messages localisés en utilisant // la syntaxe commentée ci-dessous: //====================================== // <L10N> :: FRENCH_LOCALE :: GERMAN_LOCALE //====================================== // Yes :: Oui :: Ja // I love :: J'aime :: Ich liebe // </L10N> :: // 2. Conservez le bloc ci-dessous dans votre 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. Utilisez la syntaxe __(<string>) partout où // vous avez besoin de localiser votre texte: alert( __("I love") + " Indiscripts.com!");
Le fichier YaltSample.js fournit le code générique complet à partir duquel élaborer votre propre projet.
Comments
Même en vacances, Marc continue à développer, à pondre du code et reste pédago dans ses démonstrations.
Où est le bouton On/Off ?:-)
NB : et du code commenté de surcroît…!
> Où est le bouton On/Off ?
Euh, JC, c'est l'hôpital qui se moque de l'infirmerie, là !-)
@+
Mais moi, c'est pas une activité intelligente, faire des billets c'est à la portée de tous… Pondre du script, c'est heu, ben… Pas à la portée de tous :-)