Translations / i18n / localizations for programs/fr

From Free Pascal wiki

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) 한국어 (ko) português (pt) русский (ru) 中文(中国大陆)‎ (zh_CN)

Vue d'ensemble

Cette page concerne la façon dont un programme peut utiliser différentes chaînes pour des langues aussi variées que l'anglais, le chinois, l'allemand, le finnois, l'italien, etc. Fondamentalement, cela fonctionne comme ceci :

* ajoutez une resourcestring pour chaque légende (caption) ;
* compilez pour obtenir les fichiers .rst et/ou .po (l'EDI peut le faire automatiquement) ;
* créez un fichier traduit .po pour chaque langue (il existe des outils visuels libres pour ce faire) ;
* utilisez les fonctions de l'unité translations de la LCL pour charger la bonne version de traduction au lancement du programme.

Date, heure et format de nombres

Sous Linux, BSD et Mac OS X existent différentes valeurs "locales" définissant des éléments comme le format de l'heure et de la date ou le séparateur des milliers. Afin d'initialiser la RTL, vous devez inclure l'unité clocale dans la section uses de votre programme (fichier .lpr).

Les chaînes de ressources

Par exemple :

resourcestring
    Caption1 = 'Du texte';
    HelloWorld1 = 'Bonjour, monde';

Elles sont comme des constantes de chaîne normales, ce qui veut dire que vous pouvez les assigner à n'importe quelle chaîne. Par exemple :

Label1.Caption := HelloWorld1;

Quand FPC les compile, il crée pour chaque unité un fichier unitname.rst contenant les données des chaînes de ressources (nom + contenu).

Les fichiers .po

Il existe de nombreux outils visuels libres pour éditer les fichiers .po, qui sont de simples fichiers de texte comme les fichiers .rst, mais avec quelques options supplémentaires, comme un en-tête contenant des champs indiquant le nom de l'auteur, le type d'encodage, la langue et la date. Chaque installation de FPC met à disposition l'outil rstconv (Windows : rstconv.exe). Cet outil peut être utilisé pour convertir un fichier .rst en un fichier .po. L'EDI peut le faire automatiquement. Exemples d'outils libres : kbabel, po-auto-translator, poedit, virtaal.

Virtaal a une mémoire de traduction contenant des paires source-cible pour les éléments que vous avez déjà traduits, et une fonction de suggestion de traduction qui montre les termes déjà traduits dans différents paquets de logiciels open source. Cette fonction peut vous permettre d'économiser beaucoup de travail et d'améliorer la cohérence des traductions.


Exemple utilisant directement rstconv :

 rstconv -i unit1.rst -o unit1.po

La traduction

Pour chaque langue, le fichier .po doit être copié et traduit. L'unité translation de la LCL utilise les codes habituels des langues(en=anglais, de=allemand, it=italien, ...) pour ses recherches. Par exemple, la traduction française de unit1.po serait unit1.fr.po. Pour atteindre ce résultat, le fichier unit1.po sera copié en unit1.fr.po, unit1.it.po, et toute autre langue que vous souhaitez supporter. Les traducteurs pourront alors éditer leur fichier .po spécifique.

Note-icon.png

Remarque: Pour les Brésiliens/Portugais: L'EDI de Lazarus et la LCL possèdent uniquement les traductions du Portugais Brésilien et ces fichiers ont pour extension 'pt_BR.po' (et non 'pt.po').

Les options de l'EDI pour la mise à jour des fichiers .po

  • L'unité contenant les ressources de chaînes doit être ajoutée au paquet ou au projet.
  • Vous devez définir un chemin pour les .po, ce qui signifie un dossier qui leur est propre. Par exemple : créez un sous-répertoire language dans le répertoire du paquet/projet. Pour les projets, allez dans Projet > Options du projet. Pour les paquets, allez dans Options > Intégration à l'EDI.

Quand ces options sont activées, l'EDI génère ou met à jour le fichier .po de base en utilisant les informations contenues dans les fichiers .rst et .lrt (l'outil rstconv n'est alors pas nécessaire). Le processus de mise à jour commence en collectant toutes les entrées existantes trouvées dans le fichier .po de base et dans les fichiers .rst et .lrt pour continuer en appliquant les caractéristiques qu'il trouve ensuite réactualisées dans n'importe quel fichier .xx.po de traduction.

Suppression des entrées obsolètes

Les entrées du fichier .po de base qui ne sont pas trouvées dans les fichiers .rst et .lrt sont supprimées. De même, toutes les entrées trouvées dans les fichiers .xx.po non trouvées dans le fichier .po de base sont également enlevées. De cette façon, les fichiers .po ne sont pas encombrés avec des entrées obsolètes et les traducteurs n'ont pas à traduire les entrées qui ne sont pas utilisées.

Entrées en double

Les entrées en double apparaissent quand pour quelque raison le même texte est utilisé pour différentes chaînes de ressources. Un exemple au hasard : dans le fichier lazarus/ide/lazarusidestrconst.pas pour la chaîne 'Gutter':

  dlfMouseSimpleGutterSect = 'Gutter';
  dlgMouseOptNodeGutter = 'Gutter';
  dlgGutter = 'Gutter';
  dlgAddHiAttrGroupGutter = 'Gutter';

Un fichier .rst converti pour ces chaînes de ressources serait similaire à ceci dans un fichier .po:

#: lazarusidestrconsts.dlfmousesimpleguttersect
msgid "Gutter"
msgstr ""
#: lazarusidestrconsts.dlgaddhiattrgroupgutter
msgid "Gutter"
msgstr ""
etc.

Les lignes commençant avec "#: " sont considérées comme des commentaires et les outils utilisés pour traduire ces entrées voient les lignes répétées msgid "Gutter" comme des entrées dupliquées produisant des erreurs ou des avertissements au chargement ou à la sauvegarde. Les entrées dupliquées sont considérées comme une éventualité normale dans un fichier .po et elles ont besoin d'avoir un certain contexte qui leur est attaché. Le mot clé msgctxt est utilisé pour ajouter un contexte aux entrées dupliquées et l'outil de mise à jour automatique utilise l'ID d'entrée (le texte à côté du préfixe "#: ") comme contexte. Pour l'exemple précédent serait produit quelque chose comme ceci :

#: lazarusidestrconsts.dlfmousesimpleguttersect
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
msgid "Gutter"
msgstr ""
#: lazarusidestrconsts.dlgaddhiattrgroupgutter
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
msgid "Gutter"
msgstr ""
etc.

Pour les fichiers .xx.po traduits, l'outil automatique effectue une vérification supplémentaire : si l'entrée dupliquée était déjà traduite, la nouvelle entrée récupère l'ancienne traduction, apparaissant alors comme traduite automatiquement.

Cette détection automatique des doublons n'est pas encore parfaite. La détection des doublons est effectuée quand des éléments sont ajoutés à la liste et il peut arriver que certaines entrées non traduites soient lues d'abord. Aussi se peut-il que plusieurs passes soient nécessaires pour récupérer tous les doublons traduits automatiquement par l'outil.

Entrées à l'origine de confusions possibles

Les changements dans les chaînes de ressources affectent les traductions. Par exemple, si une chaîne de ressource a été initialement définie comme ceci :

dlgEdColor = 'Syntax highlight';

cela produirait une entrée .po similaire à ceci :

#: lazarusidestrconsts.dlgedcolor
msgid "Syntax highlight"
msgstr ""

qui, en cas de traduction en espagnol (cet exemple a été tiré de l'historique de Lazarus), peut résulter en :

#: lazarusidestrconsts.dlgedcolor
msgid "Syntax highlight"
msgstr "Color"

Supposons que, plus tard, la chaîne de ressources a été changée en :

  dlgEdColor = 'Colors';

l'entrée .po résultante pourrait devenir :

#: lazarusidestrconsts.dlgedcolor
msgid "Colors"
msgstr ""

Notez qu'alors que l'ID demeure le même (lazarusidestrconsts.dlgedcolor), la chaîne est passée de 'Syntax highlight' à 'Colors'. Comme la chaîne a déjà été traduite, l'ancienne traduction peut ne pas correspondre au nouveau sens recherché. Pour la nouvelle chaîne, il est probable que 'Colores' serait une meilleure traduction.

L'outil de mise à jour automatique remarque cette situation et produit une entrée comme :

#: lazarusidestrconsts.dlgedcolor
#, fuzzy
#| msgid "Syntax highlight"
msgctxt "lazarusidestrconsts.dlgedcolor"
msgid "Colors"
msgstr "Color"

En terme de format de fichier .po, le préfixe "#," signifie que l'entrée est marquée avec un drapeau (fuzzy) si bien que les programmes de traduction peuvent présenter à l'utilisateur traducteur une interface visuelle adaptée à cet élément. Dans le cas qui nous intéresse, le drapeau signifierait que la traduction dans son état actuel est douteuse et nécessite d'être révisée plus attentivement par le traducteur. Le préfixe "#|" indique ce qu'était la précédente chaîne non traduite de cette entrée et donne une indication au traducteur sur pourquoi cette entrée a été marquée comme source de confusions possibles.

Traduction de fiches, de modules de données et de cadres

Quand l'option i18n est activée pour le projet/paquet, l'EDI produit automatiquement des fichiers .lrt pour chaque fiche. Il crée le fichier .lrt à l'enregistrement de l'unité. Par conséquent, si vous activez l'option pour la première fois, vous devez ouvrir chaque fiche une fois, la déplacer un petit peu à l'écran de sorte qu'elle soit modifiée, et l'enregistrer. Par exemple, si vous enregistrez une fiche unit1.pas, l'EDI crée un fichier unit1.lrt. A la compilation, l'EDI rassemble toutes les chaînes de tous les fichiers .lrt files et .rst dans un seul fichier .po (projectname.po ou packagename.po) dans le répertoire i18n.

Pour les fiches qui sont à traduire à l'exécution, vous devez affecter un traducteur à LRSTranslator (défini dans LResources) dans la section initialization de l'une de vos unités.

...
uses
  ...
  LResources;
...
...
initialization
  LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');

Néanmoins, il n'y a pas de classe TPoTranslator (i.e une classe qui traduit à partir des fichiers .po) disponible dans la LCL. Voici une implémentation possible (partiellement reprise de DefaultTranslator.pas dans la LCL): Le code suivant n'est plus nécessaire si vous utilisez une version 0.9.29 ou ultérieure de Lazarus. Ajoutez simplement l'unité DefaultTranslator dans la clause uses.

unit PoTranslator;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, typinfo, Translations;

type

 { TPoTranslator }

 TPoTranslator=class(TAbstractTranslator)
 private
  FPOFile:TPOFile;
 public
  constructor Create(POFileName:string);
  destructor Destroy;override;
  procedure TranslateStringProperty(Sender:TObject; 
    const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
 end;

implementation

{ TPoTranslator }

constructor TPoTranslator.Create(POFileName: string);
begin
  inherited Create;
  FPOFile:=TPOFile.Create(POFileName);
end;

destructor TPoTranslator.Destroy;
begin
  FPOFile.Free;
  inherited Destroy;
end;

procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
  const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
var
  s: String;
begin
  if not Assigned(FPOFile) then exit;
  if not Assigned(PropInfo) then exit;
{DO we really need this?}
  if Instance is TComponent then
   if csDesigning in (Instance as TComponent).ComponentState then exit;
{End DO :)}
  if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
  s:=FPOFile.Translate(Content, Content);
  if s<>'' then Content:=s;
end;

end.


Alternativement, vous pouvez transformer le fichier.po en .mo en utilisant msgfmt (n'est plus nécessaire non plus depuis la version 0.9.29) et utiliser simplement l'unité DefaultTranslator

...
uses
   ...
   DefaultTranslator;

qui recherchera automatiquement dans plusieurs emplacements standards un fichier .po (plus haute priorité) ou un fichier .mo (l'inconvénient est que vous devrez garder ensemble les fichiers .mo pour l'unité DefaultTranslator et les fichiers .po pour l'unité TranslateUnitResourceStrings.. Si vous utilisez l'unité DefaultTranslator, elle va essayer de détecter automatiquement la langue à partir de la variable d'environnement LANG (surchargeable en utilisant l'option --lang en ligne de commande), puis regardera dans les emplacements suivants pour la traduction (LANG représente la langue désirée ; l'extension peut être po ou mo) :

  • <Répertoire de l'application>/<LANG>/<Nom de fichier de l'application>.<ext>
  • <Répertoire de l'application>/languages/<LANG>/<Nom de fichier de l'application>.<ext>
  • <Répertoire de l'application>/locale/<LANG>/<Nom de fichier de l'application>.<ext>
  • <Répertoire de l'application>/locale/LC_MESSAGES/<LANG/><Nom de fichier de l'application>.<ext>

Avec les systèmes du genre Unix, l'emplacement peut aussi ressembler à

  • /usr/share/locale/<LANG>/LC_MESSAGES/<Nom de fichier de l'application>.<ext>

L'unité pourra aussi utiliser l'intitulé court de la langue (par exemple, s'il s'agit de "es_ES" ou "es_ES.UTF-8" et que le fichier n'existe pas, elle essayera aussi "es").

Traduction au début du programme

Pour chaque fichier .po, vous devez appeler TranslateUnitResourceStrings de l'unité translations de la LCL. Par exemple :

    {En premier lieu: ajoutez les unités "gettext" et "translations" à la clause uses}
    procedure TForm1.FormCreate(Sender: TObject);1
    var
      PODirectory, Lang, FallbackLang: String;
    begin
      PODirectory := '/path/to/lazarus/lcl/languages/';
      GetLanguageIDs(Lang, FallbackLang); // dans l'unité gettext
      TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
      MessageDlg('Titre', 'Texte', mtInformation, [mbOk, mbCancel, mbYes], 0);
    end;

Compilation des fichiers po dans l'exécutable

Si vous ne voulez pas installer les fichiers .po, mais placer tous les fichiers dans l'exécutable, utilisez ce qui suit :

  • Créez une nouvelle unité (pas une fiche !).
  • Convertissez le(s) fichier(s) .po en fichiers .lrs en utilisant le programme tools/lazres:
./lazres unit1.lrs unit1.de.po

Ceci va créer un fichier d'inclusion unit1.lrs commençant par

LazarusResources.Add('unit1.de','PO',[
  ...
  • Ajoutez le code :
uses LResources, Translations;

resourcestring
  MyCaption = 'Caption';

function TranslateUnitResourceStrings: boolean;
var
  r: TLResource;
  POFile: TPOFile;
begin
  r:=LazarusResources.Find('unit1.de','PO');
  POFile:=TPOFile.Create(False);  //Si Full=True vous pouvez craindre un crash (problème #0026021)
  try
    POFile.ReadPOText(r.Value);
    Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
  finally
    POFile.Free;
  end;
end;

initialization
  {$I unit1.lrs}
  • Appelez TranslateUnitResourceStrings au début du programme. Vous pouvez aussi choisir de le faire dans la section initialization.

Malheureusement, ce code ne compilera pas avec Lazarus 1.2.2 et antérieurs.

Pour ces versions anciennes, vous pouvez utiliser quelque chose comme cela :

type
  TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);

function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;
var
  LRes : TLResource;
  POFile : TPOFile = nil;
  SStream : TStringStream = nil;
begin
  Result := trResourceNotFound;
  LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
  if LRes <> nil then
  try
    SStream := TStringStream.Create(LRes.Value);
    POFile := TPoFile.Create(SStream, False);
    try
      if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess
      else Result := trTranslationError;
    except
      Result := trTranslationError;
    end;
  finally
    if Assigned(SStream) then SStream.Free;
    if Assigned(POFile) then POFile.Free;
  end;
end;

Exemple d'utilisation :

initialization
  {$I lclstrconsts.de.lrs}
  TranslateFromResource('lclstrconsts', 'de');
end.

Méthode indépendante de la plate-forme pour déterminer la langue système

La fonction ci-dessous retourne une chaîne qui représente la langue de l'interface utilisateur. Elle tourne sous Linux, Mac OS X et Windows.

uses
  Classes, SysUtils {ajoute dessunités supplémentaires nécessaires au code qui suit}
  {$IFDEF win32}
  , Windows
  {$ELSE}
  , Unix
    {$IFDEF LCLCarbon}
  , MacOSAll
    {$ENDIF}
  {$ENDIF}
  ;
function GetOSLanguage: string;
{méthode indépendante de la plate-forme pour lire la langue de l'interface utilisateur}
var
  l, fbl: string;
  {$IFDEF LCLCarbon}
  theLocaleRef: CFLocaleRef;
  locale: CFStringRef;
  buffer: StringPtr;
  bufferSize: CFIndex;
  encoding: CFStringEncoding;
  success: boolean;
  {$ENDIF}
begin
  {$IFDEF LCLCarbon}
  theLocaleRef := CFLocaleCopyCurrent;
  locale := CFLocaleGetIdentifier(theLocaleRef);
  encoding := 0;
  bufferSize := 256;
  buffer := new(StringPtr);
  success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
  if success then
    l := string(buffer^)
  else
    l := '';
  fbl := Copy(l, 1, 2);
  dispose(buffer);
  {$ELSE}
  {$IFDEF LINUX}
  fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
    {$ELSE}
  GetLanguageIDs(l, fbl);
    {$ENDIF}
  {$ENDIF}
  Result := fbl;
end;

Traduction de l'EDI

Fichiers

Les fichiers .po sont dans le répertoire source de Lazarus :

  • lazarus/languages => chaînes pour l'EDI
  • lazarus/lcl/languages/ => chaînes pour la LCL
  • lazarus/components/ideintf/languages/ => chaînes pour l'interface de l'EDI

Traducteurs

  • la traduction en allemand est maintenue par Joerg Braun.
  • la traduction en finnois est maintenue par Seppo Suurtarla.
  • la traduction en russe est maintenue par Maxim Ganetsky.
  • la traduction en français est maintenue par Gilles Vasseur.

Si vous voulez commencer une nouvelle traduction, demandez via le mailing si quelqu'un travaille déjà dessus.

Veuillez lire attentivement :Traduction/Internationalisation/Localisation

Voir aussi