Translations / i18n / localizations for programs/fr

From Lazarus wiki
Jump to navigationJump to search

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

Vue d'ensemble

Ceci concerne la façon dont un programme peut utiliser différentes chaînes pour différentes langues comme l'anglais, le chinois, l'allemand, le finois, l'italien, ... . Basiquement cela fonctionne comme ceci : Ajoutez une resourcestring pour chaque caption, compilez pour obtenir le(s) fichier(s) .rst et/ou .po (l'IDE peut le faire automatiquement), créez un fichier traduit .po pour chaque langue (il existe des outils graphiques libres) et utilisez les fonctions de l'unité translations de la LCL pour charger la bonnee au lancement du programme.

Date, heure et format de nombres

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

Resourcestrings

Par exemple

resourcestring
    Caption1 = 'Some text';
    HelloWorld1 = 'Hello World';

Elles sont comme des constantes de chaîne normales, ce qui veut dire que vous pouvez leur 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 resourcestring (nom + contenu).

Fichiers .po

Il existe de nombreux outils graphiques libres pour éditer les fichiers .po, qui sont de simples fichiers texte comme les fichiers .rst, mais avec quelques options supplémentaires, comme un header contenant des champs indiquant le nom de l'auteur, le type d'encodage, la langue et la date. Chaque installation de FPC met à dispositionl'outil rstconv (Windows: rstconv.exe). Cet outil peut être utilisé pour convertir un fichier .rst en un fichier .po. L'IDE 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 packages de logiciels open source. Cette fonction peut vous permettre d'économisser beaucoup de travail et améliorer la consistence.


Exemple utilisant directement rstconv:

 rstconv -i unit1.rst -o unit1.po

Traduction

Pour chaque langue, le fichier .po doit être copié et traduit. L'unité translation de la LCL utilise les codes courants des langues(en=anglais, de=allemand, it=italien, ...) pour ses recherches. Par exemple la traduction allemande de unit1.po serait unit1.de.po. Cela signifie copier le fichier unit1.po en unit1.de.po, unit1.it.po, et toute autre langue que vous souhaitez supporter puis les traducteurs pourront éditer leurs fichier .po spécifique.

Light bulb  Remarque: Pour les Brésiliens/Portugais: L'IDE 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').

Options de l'IDE pour la mise à jour des fichiers .po

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

Quand ces options sont activées, l'IDE génère ou met à jour les fichiers .po de base en utilisant les informations containues 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 et appliquer alors les caractéristiques suivantes qu'il trouve et réactualise tout fichier .xx.po traduit.

Retrait 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 enlevées. Par conséquent, 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înee '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.

Là où les lignes commences 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 et produisent 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 sont 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 il 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 additionnelle: si l'entrée dupliquée était déjà traduite, la nouvelle entrée prend l'ancienne traduction, alors elle apparaît comme étant traduite automatiquement.

Cette détection automatique des doublons n'est pas encore parfaite, la détection des doublons est faite quand des éléments sont sont ajouté à la liste et il peut arriver que certaines entrées non traduites soient lues d'abord. Alors il se peut que plusieurs passes soient nécessaires pour obtenir automatiquement toutes les doublons traduits automatiquement par l'outil.

Entrées floues

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

dlgEdColor = 'Syntax highlight';

ceci produirait une entrée .po similaire à ceci

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

qui si traduit en espagnol (cet exemple a été rité de l'historique de Lazarus), peut résulter en

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

Supposons alors que plus tard, la chapine de ressource a été changée pour

  dlgEdColor = 'Colors';

l'entrée .po résultant pourrait devenir

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

Notez que pendant que l'ID demeure le même lazarusidestrconsts.dlgedcolor la chapine a changé pour de 'Syntax highlight' à 'Colors'. Alors que la chapine a déjà été traduite l'ancienne traduction peut ne pas correspondre au nouveau sens. Toutefois, pour la nouvelle chaîne ils est probable que 'Colores' pourrait être 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 "#," signigie que l'entrée a un flag (fuzzy) et les programme sde tradution peuvent présenter un GUI à l'utilisateur traducteur pour cet élément. Dans ce cas, le flag signifierait que la traduction dans sont état courant est douteux et nécessite d'être révisé plus attentivement par le traducteur. Le préfixe "#|" indique ce qu"tait la précédente chapine non traduite de cette entrée et donne une indication au traducteur pourquoi cette entrée a été marquée comme fuzzy.

Traduction de Fiches, Modules de données et Cadres

Quand l'option i18n est activée pour le projet / paquet, alors l'EDI produit automatiquement des fichiers .lrt pour chaque fiche. Il crée le fichier .lrt à l'enregistrement de l'unité. Donc, si vous activez l'option pour la première fois, vous devez ouvrir chaque fiche une fois, la changer un petit peu, de sorte qu'il soit modifié, et enregistrez la fiche. Par exemple, si vous sauvez une fiche unit1.pas l'EDI crée un fichier unit1.lrt. Et à 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 depuis la version 0.9.29 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 plius nécessaire depuis la version 0.9.29) et utiliser simplement l'unité DefaultTranslator

...
uses
   ...
   DefaultTranslator;

qui inspectera automatiquement dans plusieurs emplacements standards pour 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 DefaultTranslator, il va essayer automatiquement de détecter la langue à partir de la variable d'environnement LANG (surchargeable en utilisant l'option --lang en ligne de commande), puis regardera dans ces emplacements pour la traduction (LANG veut dire 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>

sous les systèmes à la Unix, cela ressemble aussi à

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

aussi bien en utilisant la petite partie de la langue (par exemple s'il s'agit de "es_ES" ou "es_ES.UTF-8" et qu'il n'existe pas il sera aussi essayé "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('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
    end;

Compilation des fichiers po dans l'exécutable

Si vous ne voulez pas installer les fichiers .po, mais mettre 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 file(s) en .lrs utilisant 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 alors vous pouvez avoir 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 le faire dans la section initialization si vous voulez.

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

Pour ces versions, 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 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

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

SVP, lire attentivement:Traduction/Internationalisation/Localisation

Voir aussi