Suivez-nous sur X
|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - la locale.library
(Article écrit par Frédéric Delacroix et extrait d'Amiga News - mai 1995)
|
|
Depuis maintenant quelques années, la locale.library fait partie de notre usage quotidien du Workbench. Elle permet de franciser
non seulement le Workbench, mais aussi tout programme sachant tirer parti de ses possibilités par le biais d'un simple fichier
rajouté par l'utilisateur dans le bon tiroir.
Présentation
Avant le Kickstart 2.1, un programme voulant fonctionner dans plusieurs langues devait se débrouiller tout seul. Certains le
faisaient assez bien en présentant, de manière très similaire au comportement de la locale.library, des fichiers séparés pour
chaque langue, de façon à ce qu'un Français n'occupe pas inutilement son disque dur avec du Norvégien ou du Polonais.
D'autres, en revanche, ne s'embarrassaient pas de ce genre de considérations et codaient brutalement les différentes langues
dans leurs exécutables. C'est pour normaliser et faciliter, tant du point de vue programmeur qu'utilisateur, la polyglottie
des applications que Commodore a introduit la locale.library.
Concrètement, l'utilisateur peut ajouter une langue à une application particulière, à condition qu'il dispose du fichier
catalogue adéquat, en plaçant celui-ci dans le répertoire du langage considéré, lui-même se trouvant soit dans le répertoire
système Locale:Catalogs/, soit dans le répertoire "Catalogs" du répertoire de l'application (auquel on accède par la pseudo-assignation
PROGDIR:). Ce système permet une grande souplesse pour l'utilisateur, qui peut configurer ses choix à l'aide du
programme de préférences de la locale.library.
Du point de vue du programmeur, cela oblige évidemment à quelques adaptations, mais si un programme est conçu dès le début dans
cette optique, cela ne pose aucun problème. De plus, le choix de l'utilisateur est absolument transparent pour le programme.
Enfin, la locale.library propose beaucoup d'autres fonctions permettant à un programme de s'adapter aux normes en vigueur dans
le pays où il tourne, comme la gestion des différents formats numériques, de dates, une fonction de formatage dans le style
RawDoFmt() mais permettant de changer l'ordre des arguments, une gigantesque structure contenant des tas d'informations utiles.
Contraintes
Chaque chaîne de caractères susceptible de varier selon les langues doit être dans un fichier nommé Catalog Description File
(son extension est en général .cd) qui sera utilisé par le programme CatComp pour générer des fichiers complémentaires, comme
un fichier include pour le C ou l'assembleur, un fichier objet pour le lien (linkage), un fichier de traduction de catalogue
(ceux-là se terminent par .ct) qui sera utilisé pour indiquer les chaînes de caractères correspondant à une autre langue, et,
bien entendu, les fichiers catalogues.
J'ai parlé de remplir les fichiers .cd, mais avec quoi ? Avec les chaînes de caractères dans la langue par défaut, il est
préférable que ce soit l'anglais. Chaque chaîne de caractères se voit attribuer un identificateur qui est un entier sur 32 bits.
Ces identificateurs sont toujours représentés par une étiquette commençant par MSG_, en majuscules. Choisissez-la suffisamment
explicite pour que vos traducteurs éventuels la comprennent, et toujours en anglais.
Toutes les références à des messages se feront obligatoirement par cet identificateur et par l'intermédiaire des fonctions de la
locale.library. Pour compiler correctement vos catalogues et générer tous les fichiers annexes, vous aurez de toute façon besoin
du programme CatComp qui se trouve sur le paquetage développeur (disponible chez Someware par exemple). Reportez-vous à sa
documentation pour sa syntaxe exacte.
Concrètement
L'ouverture de la locale.library se fait normalement par OpenLibrary(). Réclamez toujours la version 38. Une fois ceci fait
(n'oubliez pas de tester le résultat !). Vous pouvez ouvrir la locale courante (c'est-à-dire une bibliothèque partagée, un peu
spéciale dont la structure exacte n'est connue que de la locale.library) grâce à la fonction OpenLocale().
Celle-ci admet en A0 un pointeur sur le nom de la locale à ouvrir, 0 signifiant la locale actuelle, ce qui doit toujours être le
cas, sauf effet spécial désiré. En retour, D0 pointe sur une structure Locale, ou vaut 0 en cas d'erreur (ce ne sera jamais le
cas avec la locale courante). Cette structure Locale contient tout un tas d'informations utiles comme le format de la date dans
le pays ou la lettre réservée pour la monnaie. Elle sera en outre nécessaire pour (presque) toutes les opérations de la
locale.library, mais pas forcément pour l'ouverture d'un catalogue.
Vous pouvez ouvrir le catalogue de votre application grâce à la fonction OpenCatalog() : pointeur sur la Locale en A0 (précédemment
ouverte par OpenLocale(), ou 0 pour la locale courante, ce qui sera généralement le cas), nom en A1, pointeur sur une liste de balises en
A2. Les tags actuellement définis permettent de réclamer un langage particulier (OC_Language), une version précise (OC_Version),
de préciser le langage des chaînes incluses dans votre exécutable (OC_BuiltInLanguage). Attention, le numéro de version ne
fonctionne pas comme celui d'OpenLibrary() : il faut que la version du catalogue soit exactement celle demandée. En retour, on
obtient un pointeur qui ne signifie quelque chose que pour la locale.library. 0 signifie que le catalogue n'a pu être ouvert pour
une raison ou pour une autre.
L'accès aux messages contenus dans le catalogue se fait par l'intermédiaire de la fonction GetCatalogStr() : pointeur sur le
catalogue retourné par OpenCatalog() en A0, identificateur de la chaîne à renvoyer en D0 et pointeur sur la chaîne dans la langue
par défaut en A1. C'est ce message qui est retourné quand le message en question n'existe pas dans le catalogue, par exemple
lorsque CatComp remarque que la chaîne traduite et la chaîne originale sont les mêmes. En D0, la fonction retourne un pointeur
sur le message voulu, pour utilisation par votre application. Ce pointeur reste valable tant que le catalogue reste ouvert.
Bien entendu, vous ne devez jamais modifier ce message.
Lorsque votre programme est sur le point de quitter, il faut tout fermer : fermer le catalogue grâce à CloseCatalog(), la Locale
grâce à CloseLocale(), et la locale.library par CloseLibrary().
Il existe aussi une fonction, d'usage assez rare toutefois, GetLocaleStr(), permettant l'accès à des messages courants (Oui, Non,
Aujourd'hui...) que la locale connaît. Même syntaxe que pour GetCatalogStr(), sauf le paramètre catalogue, qui est remplacé par
un pointeur sur la Locale, et la chaîne par défaut, qui n'existe pas. Voyez les includes pour toutes les possibilités.
Autres possibilités
La locale.library offre d'autres fonctions. Celles qui concernent la date sont FormatDate(), qui permet de préparer une date grâce
à des codes de formatage similaires à ceux utilisés par RawDoFmt() et printf(), par exemple %A pour le nom du jour, %a pour le nom
abrégé, %Y pour l'année. L'autre, ParseDate(), tente de faire l'opération inverse : interpréter une chaîne de caractères
représentant une date, dans la langue considérée bien sûr.
D'autres fonctions concernent la gestion des caractères : ConvToUpper() et ConvToLower() pour mettre en majuscules ou minuscules,
FormatString() est un remplacement de RawDoFmt() qui permet de faire varier la position des arguments (très utile), la série des
IsXXX() permet de tester si un caractère est une majuscule, un symbole de ponctuation, un chiffre hexadécimal... et Strncmp() et
StrConvert() proposent des algorithmes sophistiqués de comparaison (comment comparer les lettres e et é en français ?) et
transformation de chaînes de caractères. Je vous renvoie aux autodocs pour plus de détails car la place m'est comptée.
Exemple
Le "programme de mai 95 que j'ai" vous permet d'examiner quelques champs d'une structure locale : formats de date et heure,
symboles monétaires, décalage horaire, etc. C'est bien entendu un programme localisé, il vous faudra impérativement vous procurer
le programme CatComp. Vous devrez taper le fichier ShowLocale.cd et fabriquer un fichier include pour l'assembleur en tapant :
Catcomp ShowLocale.cd ASMFILE=ShowLocaleCat.i
|
Vous pourrez alors assembler le programme normalement. Lorsque vous le lancerez, vous constaterez que les messages sont en anglais.
Pour effectuer une traduction, c'est devenu très simple : fabriquez un fichier .ct vierge par la commande :
Catcomp ShowLocale.cd CTFILE=ShowLocale.ct
|
Éditez alors le fichier résultant, en remplissant les blancs par les traductions françaises (ou autres !) des messages placés
en commentaire. Remplacez également les X par ce qu'il faut : ShowLocale.catalog au début du fichier, le numéro de version
(1.0) et la date, le codeset à 0, et language à français (n'oubliez pas la cédille, et mettez tout en minuscules). Vous
sauvegardez à nouveau le fichier .ct et vous pouvez alors générer le catalogue lui-même par :
Catcomp ShowLocale.cd ShowLocale.ct CATALOG=LOCALE:Catalogs/ français/ShowLocale.catalog
|
Après cela, le programme devrait marcher en français, comme par miracle. Un dernier mot : le programme attend un argument.
C'est le nom de la locale à ouvrir, c'est-à-dire le nom d'un fichier créé avec l'éditeur de préférences de la locale.
Si vous ne donnez aucun nom, c'est la locale en cours qui sera utilisée (fichier locale.prefs).
Fichier.cd
#header SHOWLOCALE_MESSAGES
#array ShowLocale.Mes
;
MSG_NOLOCALE (//)
Cannot open required locale.
;
MSG_LOCALENAME (//)
Locale name: "%s"
;
MSG_LANGUAGENAME (//)
Language name: "%s"
;
MSG_COUNTRYCODE (//)
Country code: %lU
;
MSG_TELEPHONECODE (//)
Telephone code: %lU
;
MSG_GMTOFFSET (//)
Greenwich Meridian Time offset (minutes): %lD
;
MSG_DATEFORMAT (//)
Date format: "%s"
;
MSG_TIMEFORMAT (//)
Time format: "%s"
;
MSG_SHORTDATEFORMAT (//)
Short date format: "%s"
;
MSG_SHORTTIMEFORMAT (//)
Short time format: "%s"
;
MSG_DECIMALPOINT (//)
Decimal point: "%s"
;
MSG_GROUPSEPARATOR (//)
Group separator: "%s"
;
MSG_FRACGROUPSEPARATOR (//)
Fractional group separator: "%s"
;
MSG_MONCS (//)
Currency symbol: "%s"
;
MSG_MONINTCS (//)
Internationnal currency code (ISO 4217): "%s"
|
Programme principal
; listing du programme: ShowLocale.s
; n'oubliez pas de produire le fichier include
; ShowLocaleCat.i avec Catcomp avant d'assembler
include exec/exec.i
include exec/exec_lib.i
include dos/dos.i
include dos/dos_lib.i
include libraries/locale.i
include libraries/locale_lib.i
move.l 4.w,a6
lea DOS.Name(pc),a1
moveq #37,d0
jsr _LVOOpenLibrary(a6)
move.l d0,DOS.Base
beq exit
lea Locale.Name(pc),a1
moveq #38,d0
jsr _LVOOpenLibrary(a6)
move.l d0,Locale.Base
beq closedos
suba.l a0,a0 ; locale courante
move.l d0,a6
jsr _LVOOpenLocale(a6)
move.l d0,CurrentLocale
suba.l a0,a0
lea Catalog.Name(pc),a1
move.l a0,a2
jsr _LVOOpenCatalogA(a6)
move.l d0,Prog.Catalog
move.l #Args.Template,d1
move.l #Args.Array,d2
moveq #0,d3
move.l DOS.Base(pc),a6
jsr _LVOReadArgs(a6) ; lit les args
move.l d0,Args.RDArgs
beq.s closecatalog
jsr _LVOOutput(a6)
move.l d0,Out.Handle ; canal de sortie
move.l Args.Array(pc),a0 ; ouvre locale
move.l Locale.Base(pc),a6 ; demandée
jsr _LVOOpenLocale(a6)
move.l d0,Locale.ToShow
bne.s LocaleFound
move.l #MSG_NOLOCALE,d0 ; non trouvée
bsr Print
bra.s freeargs
LocaleFound
lea Print.Hook(pc),a3 ; routine d'affichage
lea Fields.Table(pc),a4 ; champs à afficher
move.l Locale.ToShow(pc),a5
move.l Locale.Base(pc),a6
MainLoop
move.l (a4)+,d0 ; decalage du champ
blt.s .End ; <0 => fin
move.l 0(a5,d0),-(sp)
move.l CurrentLocale(pc),a0
move.l (a4)+,d0 ; message réel
bsr.s GetString
move.l d0,a1
move.l sp,a2
jsr _LVOFormatString(a6)
addq.l #4,sp
bra.s MainLoop
.End move.l Locale.ToShow(pc),a0
move.l Locale.Base(pc),a6
jsr _LVOCloseLocale(a6)
freeargs
move.l Args.RDArgs(pc),d1
move.l DOS.Base(pc),a6
jsr _LVOFreeArgs(a6)
closecatalog
move.l Prog.Catalog(pc),a0
move.l Locale.Base(pc),a6
jsr _LVOCloseCatalog(a6)
move.l CurrentLocale(pc),a0
jsr _LVOCloseLocale(a6)
move.l a6,a1
move.l 4.w,a6
jsr _LVOCloseLibrary(a6)
closedos
move.l DOS.Base(pc),a1
jsr _LVOCloseLibrary(a6)
exit moveq #0,d0
rts
; cette routine renvoie le message dont
; l'identificateur est en D0
GetString
movem.l d1/a0-a1/a6,-(sp)
lea ShowLocale.Mes(pc),a1
.sloop cmp.l cca_ID(a1),d0
beq.s .found
addq.l #ShowLocale.MesType_SIZEOF,a1
bra.s .sloop
.found move.l cca_Str(a1),a1
move.l Prog.Catalog(pc),a0
move.l Locale.Base(pc),a6
jsr _LVOGetCatalogStr(a6)
movem.l (sp)+,d1/a0-a1/a6
rts
Print movem.l d0-d1/a0-a1/a6,-(sp)
bsr GetString
move.l DOS.Base(pc),a6
move.l d0,d1
jsr _LVOPutStr(a6)
movem.l (sp)+,d0-d1/a0-a1/a6
rts
; routine appelée par la fonction FormatString() de
; la locale.library
Print.Func
movem.l d0-d2/a0-a1/a6,-(sp)
move.l a1,d2
beq.s .None
move.l Out.Handle(pc),d1
move.l DOS.Base(pc),a6
jsr _LVOFPutC(a6)
.None movem.l (sp)+,d0-d2/a0-a1/a6
rts
DOS.Base dc.l 0
Locale.Base dc.l 0
CurrentLocale dc.l 0
Prog.Catalog dc.l 0
Args.Array dc.l 0
Args.RDArgs dc.l 0
Out.Handle dc.l 0
Locale.ToShow dc.l 0
Print.Hook dc.l 0,0,Print.Func,0,0
DOS.Name dc.b 'dos.library',0
Locale.Name dc.b 'locale.library',0
Catalog.Name dc.b 'ShowLocale.catalog',0
Args.Template dc.b 'LOCALENAME',0
even
Fields.Table
dc.l loc_LocaleName,MSG_LOCALENAME
dc.l loc_LanguageName,MSG_LANGUAGENAME
dc.l loc_CountryCode,MSG_COUNTRYCODE
dc.l loc_TelephoneCode,MSG_TELEPHONECODE
dc.l loc_GMTOffset,MSG_GMTOFFSET
dc.l loc_DateFormat,MSG_DATEFORMAT
dc.l loc_TimeFormat,MSG_TIMEFORMAT
dc.l loc_ShortDateFormat,MSG_SHORTDATEFORMAT
dc.l loc_ShortTimeFormat,MSG_SHORTTIMEFORMAT
dc.l loc_DecimalPoint,MSG_DECIMALPOINT
dc.l loc_GroupSeparator,MSG_GROUPSEPARATOR
dc.l loc_FracGroupSeparator,MSG_FRACGROUPSEPARATOR
dc.l loc_MonCS,MSG_MONCS
dc.l loc_MonIntCS,MSG_MONINTCS
dc.l -1
CATCOMP_NUMBERS SET 1
CATCOMP_STRINGS SET 1
CATCOMP_ARRAY SET 1
include ShowLocaleCat.i
|
|