Difference between revisions of "Translations / i18n / localizations for programs/es"

From Free Pascal wiki
Jump to navigationJump to search
m
m (Fixed syntax highlighting; deleted category included in page template)
 
(18 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Translations_/_i18n_/_localizations_for_programs}}
 
{{Translations_/_i18n_/_localizations_for_programs}}
 
[[category:Castellano|P]][[category:Español|P]]
 
[[category:Castellano|P]][[category:Español|P]]
==Descripción general==
+
==Descripción general sobre internacionalización (i18n)==
  
   Cómo hacer que los programas utilicen diferentes cadenas para varios idiomas cómo inglés, chino, alemán, castellano, etc. Básicamente funciona de la siguiente forma: Añade un recurso de cadena (''resourcestring'') para cada literal, compila para obtener los archivos .rst y/o .po (el IDE lo hace automáticamente), crea un archivo .po para cada idioma (existen herramientas gráficas para ello) y utiliza las funciones de la unidad ''translations'' de la LCL para cargar el archivo adecuado al iniciar el programa.
+
   ¿Cómo hacer que los programas utilicen diferentes cadenas para diversos idiomas cómo inglés, chino, alemán, castellano, etc.?
 +
 
 +
Básicamente funciona de la siguiente forma: Añade un recurso de cadena (''resourcestring'') para cada literal, compila para obtener los archivos .rst y/o .po (el IDE lo hace automáticamente), crea un archivo .po para cada idioma (existen herramientas gráficas para ello) y utiliza las funciones de la unidad ''translations'' de la LCL para cargar el archivo adecuado al iniciar el programa.
  
 
==Formatos de fechas, horas y números==
 
==Formatos de fechas, horas y números==
Line 12: Line 14:
  
 
   Por ejemplo
 
   Por ejemplo
<delphi>  resourcestring
+
<syntaxhighlight lang=pascal>  resourcestring
 
     Etiqueta1 = 'Algo de texto';
 
     Etiqueta1 = 'Algo de texto';
     HolaMundo1 = 'Hola Mundo';</delphi>
+
     HolaMundo1 = 'Hola Mundo';</syntaxhighlight>
  
 
&nbsp;&nbsp;&nbsp;Son como constantes de cadena normales, lo que significa que se pueden asignar a cualquier cadena. Por ejemplo
 
&nbsp;&nbsp;&nbsp;Son como constantes de cadena normales, lo que significa que se pueden asignar a cualquier cadena. Por ejemplo
<delphi>  Label1.Caption := HolaMundo1;</delphi>
+
<syntaxhighlight lang=pascal>  Label1.Caption := HolaMundo1;</syntaxhighlight>
  
 
&nbsp;&nbsp;&nbsp;Al compilar se creará un archivo .rst por cada unidad, con el mismo nombre que esta, conteniendo los resursos de cadena (nombre y contenido)
 
&nbsp;&nbsp;&nbsp;Al compilar se creará un archivo .rst por cada unidad, con el mismo nombre que esta, conteniendo los resursos de cadena (nombre y contenido)
Line 44: Line 46:
  
 
&nbsp;&nbsp;&nbsp;Las entradas en el archivo .po base no se encontradas en el archivo en los archivos .rst y .lrt se eliminan, posteriormente, todas las entradas que se encuentran en archivos .xx.po traducidos  que no se encuentran en el archivo .po base también se quitan. De esta manera los archivos .po se limpian de las entradas obsoletas y los traductores no tienen que traducir las entradas que no se utilizan.
 
&nbsp;&nbsp;&nbsp;Las entradas en el archivo .po base no se encontradas en el archivo en los archivos .rst y .lrt se eliminan, posteriormente, todas las entradas que se encuentran en archivos .xx.po traducidos  que no se encuentran en el archivo .po base también se quitan. De esta manera los archivos .po se limpian de las entradas obsoletas y los traductores no tienen que traducir las entradas que no se utilizan.
===Duplicate entries===
 
  
Duplicate entries occur when for some reason the same text is used for diferent resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:
+
===Entradas duplicadas===
<Delphi>
+
 
  dlfMouseSimpleGutterSect = 'Gutter';
+
&nbsp;&nbsp;&nbsp;Las entradas duplicadas se producen cuando por alguna razón se utiliza el mismo texto para cadenas de recursos diferentes, un ejemplo, de esto es el archivo de Lazarus/ide/lazarusidestrconst.pas para la cadena 'Gutter';
  dlgMouseOptNodeGutter = 'Gutter';
+
 
  dlgGutter = 'Gutter';
+
<syntaxhighlight lang=pascal> dlfMouseSimpleGutterSect = 'Gutter';
  dlgAddHiAttrGroupGutter  = 'Gutter';
+
dlgMouseOptNodeGutter = 'Gutter';
</Delphi>
+
dlgGutter = 'Gutter';
A converted .rst file for this resource strings would look similar to this in a .po file:
+
dlgAddHiAttrGroupGutter  = 'Gutter';</syntaxhighlight>
 +
 
 +
&nbsp;&nbsp;&nbsp;El archivo .rst de recursos para estas cadenas
 +
A converted .rst file for this resource strings tendría un aspecto similar a este en un archivo .po:
  
#: lazarusidestrconsts.dlfmousesimpleguttersect
+
  #: lazarusidestrconsts.dlfmousesimpleguttersect
msgid "Gutter"
+
  msgid "Gutter"
msgstr ""
+
  msgstr ""
#: lazarusidestrconsts.dlgaddhiattrgroupgutter
+
  #: lazarusidestrconsts.dlgaddhiattrgroupgutter
msgid "Gutter"
+
  msgid "Gutter"
msgstr ""
+
  msgstr ""
etc.
+
  etc.
+
 
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:
+
&nbsp;&nbsp;&nbsp;Donde las líneas que comienzan con "#:" se consideran como los comentarios y las herramientas empleadas para traducir estas entradas verán las repetidas líneas ''msgstr "Gutter"'' como entradas duplicadas y provocarán errores o advertencias al cargar o guardar. Las entradas duplicadas se consideran eventualmente normales en archivos .po y necesitan tener algo de contexto asociado. La palabra clave ''msgctxt'' se utiliza para agregar contexto a las entradas duplicadas y la herramienta de actualización automática de utiliza la entrada ID (el texto que sigue a "#:" ) como el contexto, para el ejemplo anterior se produciría algo así como esto:
  
#: lazarusidestrconsts.dlfmousesimpleguttersect
+
  #: lazarusidestrconsts.dlfmousesimpleguttersect
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
+
  msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
msgid "Gutter"
+
  msgid "Gutter"
msgstr ""
+
  msgstr ""
#: lazarusidestrconsts.dlgaddhiattrgroupgutter
+
  #: lazarusidestrconsts.dlgaddhiattrgroupgutter
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
+
  msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
msgid "Gutter"
+
  msgid "Gutter"
msgstr ""
+
  msgstr ""
etc.
+
  etc.
+
&nbsp;&nbsp;&nbsp;En los archivos traducidos .xx.po la herramienta automática hace una comprobación adicional: si la entrada duplicada fue traducida ya, la nueva entrada toma la traducción previa, por lo que aparece como traducida automáticamente.
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.
 
  
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.
+
&nbsp;&nbsp;&nbsp;La detección automática de duplicados aún no es perfecta, la detección se realiza cuando se agregan elementos a la lista y puede suceder que algunas entradas sin traducir se lean primero. Por lo tanto, puede llevar varias pasadas obtener todos los duplicados traducidos automáticamente por la herramienta.
===Fuzzy entries===
 
  
Changes in resource strings affect translations, for example if initially a resource string was defined like:
+
===Entradas confusas===
<Delphi>
 
  dlgEdColor = 'Syntax highlight';
 
</Delphi>
 
this would produce a .po entry similar to this
 
#: lazarusidestrconsts.dlgedcolor
 
msgid "Syntax higlight"
 
msgstr ""
 
which if translated to spanish langauge (this sample was taken from lazarus history), may result in
 
#: lazarusidestrconsts.dlgedcolor
 
msgid "Syntax higlight"
 
msgstr "Color"
 
Suppose then that at a later time, the resource string has been changed to
 
<Delphi>
 
  dlgEdColor = 'Colors';
 
</Delphi>
 
the resulting .po entry may become
 
#: lazarusidestrconsts.dlgedcolor
 
msgid "Colors"
 
msgstr ""
 
Note that while ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors', as the string was already translated the old translation may not match the new the new meaning. Indeed, for the new string probably 'Colores' may be a better translation.
 
The automatic update tool notices this situation and produce an entry like this:
 
#: lazarusidestrconsts.dlgedcolor
 
#, fuzzy
 
#| msgid "Syntax highlight"
 
msgctxt "lazarusidestrconsts.dlgedcolor"
 
msgid "Colors"
 
msgstr "Color"
 
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present to the translator user a special gui for this item, in this case the flag would mean that the translation in its current state is doubtful and needs to be reviewed more careful by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.
 
  
 +
&nbsp;&nbsp;&nbsp;Los cambios en las cadenas de recursos afectan a las traducciones, por ejemplo cuando en un principio una cadena de recursos se define así:
 +
<syntaxhighlight lang=pascal> dlgEdColor = 'Syntax highlight';</syntaxhighlight>
 +
&nbsp;&nbsp;&nbsp;esto produce una entrada en .po similar a esta
 +
  #: lazarusidestrconsts.dlgedcolor
 +
  msgid "Syntax higlight"
 +
  msgstr ""
 +
&nbsp;&nbsp;&nbsp;que si se han traducido al idioma castellano (este ejemplo está tomado de la historia de lazarus), puede resultar en
 +
  #: lazarusidestrconsts.dlgedcolor
 +
  msgid "Syntax higlight"
 +
  msgstr "Color"
 +
&nbsp;&nbsp;&nbsp;Supongamos ahora que, en un momento posterior, la cadena de recursos se ha cambiado a
 +
<syntaxhighlight lang=pascal> dlgEdColor = 'Colors';</syntaxhighlight>
 +
&nbsp;&nbsp;&nbsp;la entrada en .po puede ser
 +
  #: lazarusidestrconsts.dlgedcolor
 +
  msgid "Colors"
 +
  msgstr ""
 +
&nbsp;&nbsp;&nbsp;Tenga en cuenta que, si bien el ID sigue siendo igualmente ''lazarusidestrconsts.dlgedcolor'' la cadena ha cambiado de "Syntax highlight' a 'Colors', dado que la cadena se tradujo ya la traducción previa no puede coincidir con el nuevo significado. De hecho, para la nueva cadena probablemente 'Colores' puede ser una traducción mejor.
 +
&nbsp;&nbsp;&nbsp;La herramienta de actualización automática avisa de esta situación y produce una entrada como esta:
 +
  #: lazarusidestrconsts.dlgedcolor
 +
  #, fuzzy
 +
  #| msgid "Syntax highlight"
 +
  msgctxt "lazarusidestrconsts.dlgedcolor"
 +
  msgid "Colors"
 +
  msgstr "Color"
 +
&nbsp;&nbsp;&nbsp;En los términos del formato de los archivos .po, el prefijo "#," significa que la entrada tiene una marca (confusa) y los programas traductores puede presentar al usuario un igu especial para este elemento, en este caso la marca significa que la traducción en su estado actual es dudosa y hay que revisarla con más cuidado por el traductor. El prefijo "#|" indica cuál fue la cadena anterior no traducida de esta entrada y le da al traductor una idea del porqué  la entrada estaba marcada como confusa.
  
 
==Traduciendo Formularios, Módulos de Datos y Marcos==
 
==Traduciendo Formularios, Módulos de Datos y Marcos==
Line 116: Line 115:
  
 
&nbsp;&nbsp;&nbsp;Para los formularios que se traducen en tiempo de ejecución, hay que asignar un valor  que apunte al achivo de traducción a ''LRSTranslator'' (definido en ''LResources'') en la sección de inicio de una de las unidades del proyecto.
 
&nbsp;&nbsp;&nbsp;Para los formularios que se traducen en tiempo de ejecución, hay que asignar un valor  que apunte al achivo de traducción a ''LRSTranslator'' (definido en ''LResources'') en la sección de inicio de una de las unidades del proyecto.
<Delphi> ...
+
<syntaxhighlight lang=pascal> ...
 
  uses
 
  uses
 
   ...
 
   ...
Line 123: Line 122:
 
  ...
 
  ...
 
  initialization
 
  initialization
   LRSTranslator:=TPoTranslator.Create('/ruta/al/archivo/po'); </Delphi>
+
   LRSTranslator:=TPoTranslator.Create('/ruta/al/archivo/po'); </syntaxhighlight>
  
&nbsp;&nbsp;&nbsp;La clase que traduce utilizando archivos ''.po'', TPoTranslator, no está disponible en la LCL. Esta es una posible implementación (tomada en parte  de ''DefaultTranslator.pas'' de la LCL):
+
&nbsp;&nbsp;&nbsp;<s>La clase que traduce utilizando archivos ''.po'', TPoTranslator, no está disponible en la LCL. Esta es una posible implementación (tomada en parte  de ''DefaultTranslator.pas'' de la LCL)</s> El código siguiente ya no se necesita si utilizas Lazarus 0.9.29. Sólo tienes que incluir ''DefaultTranslator'' en la cláusula ''uses'':
  
<Delphi> unit PoTranslator;
+
<syntaxhighlight lang=pascal> unit PoTranslator;
  
 
  {$mode objfpc}{$H+}
 
  {$mode objfpc}{$H+}
Line 182: Line 181:
 
  end;
 
  end;
  
  end. </Delphi>
+
  end. </syntaxhighlight>
  
 
&nbsp;&nbsp;&nbsp;Alternativamente se puede transformar el archivo ''.po'' en una ''.mo'', utilizando msgfmt, y utilizar la unidad ''DefaultTranslator''.
 
&nbsp;&nbsp;&nbsp;Alternativamente se puede transformar el archivo ''.po'' en una ''.mo'', utilizando msgfmt, y utilizar la unidad ''DefaultTranslator''.
<Delphi> ...
+
<syntaxhighlight lang=pascal> ...
 
  uses
 
  uses
 
  ...
 
  ...
  DefaultTranslator;</Delphi>
+
  DefaultTranslator;</syntaxhighlight>
  
&nbsp;&nbsp;&nbsp;con ello se buscará automáticamente en varios lugares preestablecidos un archivo ''.mo''; la desventaja es que hay que mantener ambos, los archivos ''.mo'' para la unidad ''DefaultTranslator'' y los archivos ''.po'' para ''TranslateUnitResourceStrings''.
+
&nbsp;&nbsp;&nbsp;con ello se buscará automáticamente en varios lugares preestablecidos un archivo ''.po'' (primero) o un archivo ''.mo''; la desventaja es que hay que mantener ambos, los archivos ''.mo'' para la unidad ''DefaultTranslator'' y los archivos ''.po'' para ''TranslateUnitResourceStrings''.
  
 
&nbsp;&nbsp;&nbsp;Si utilizamos ''DefaultTranslator'', se intentará detectar automáticamente la lengua basandose en la variable de entorno ''LANG'' (modificable utilizando la opción ''--lang'' de la línea de órdenes), buscando entonces en esos lugares la traducción (''LANG'' representa la lengua deseada):
 
&nbsp;&nbsp;&nbsp;Si utilizamos ''DefaultTranslator'', se intentará detectar automáticamente la lengua basandose en la variable de entorno ''LANG'' (modificable utilizando la opción ''--lang'' de la línea de órdenes), buscando entonces en esos lugares la traducción (''LANG'' representa la lengua deseada):
* <Application Directory>/LANG/<Application Filename>.mo
+
* <Application Directory>/LANG/<Application Filename>.po (o .mo)
* <Application Directory>/languages/LANG/<Application Filename>.mo
+
* <Application Directory>/languages/LANG/<Application Filename>.po (o .mo)
* <Application Directory>/locale/LANG/<Application Filename>.mo
+
* <Application Directory>/locale/LANG/<Application Filename>.po (o .mo)
* <Application Directory>/locale/LC_MESSAGES/LANG/<Application Filename>.mo
+
* <Application Directory>/locale/LC_MESSAGES/LANG/<Application Filename>.po (o .mo)
  
 
&nbsp;&nbsp;&nbsp;en sistemas *NIX esto se verá así
 
&nbsp;&nbsp;&nbsp;en sistemas *NIX esto se verá así
  
  * /usr/share/locale/LANG/LC_MESSAGES/<Application Filename>.mo
+
  * /usr/share/locale/LANG/LC_MESSAGES/<Application Filename>.po (o .mo)
  
 
&nbsp;&nbsp;&nbsp;también usará la parte corta del idioma (e.g. si es " es_ES" o " es_ES.UTF-8" y  este no existe también probará con "es")
 
&nbsp;&nbsp;&nbsp;también usará la parte corta del idioma (e.g. si es " es_ES" o " es_ES.UTF-8" y  este no existe también probará con "es")
Line 208: Line 207:
 
&nbsp;&nbsp;&nbsp;Para cada fichero .po hay que hacer una llamada a la función ''TranslateUnitResourceStrings'' de la unidad de ''translations'' de la LCL. Por ejemplo:
 
&nbsp;&nbsp;&nbsp;Para cada fichero .po hay que hacer una llamada a la función ''TranslateUnitResourceStrings'' de la unidad de ''translations'' de la LCL. Por ejemplo:
  
<delphi>   {lo pimero: añadir las unidades "gettext" y "translations" en la cláusula uses}
+
<syntaxhighlight lang=pascal> {lo pimero: añadir las unidades "gettext" y "translations" en la cláusula uses}
    procedure TForm1.FormCreate(Sender: TObject);
+
procedure TForm1.FormCreate(Sender: TObject);
    var
+
  var
      PODirectorio, Idioma, IdiomaDevuelto: String;
+
    PODirectorio, Idioma, IdiomaDevuelto: String;
    begin
+
  begin
      PODirectorio := '/ruta/a/lazarus/lcl/languages/';
+
    PODirectorio := '/ruta/a/lazarus/lcl/languages/';
      GetLanguageIDs(Idioma, IdiomaDevuelto); // en la unidad "gettext"
+
    GetLanguageIDs(Idioma, IdiomaDevuelto); // en la unidad "gettext"
      TranslateUnitResourceStrings('LCLStrConsts', PODirectorio + 'lclstrconsts.%s.po', Idioma, IdiomaDevuelto);
+
    TranslateUnitResourceStrings('LCLStrConsts', PODirectorio + 'lclstrconsts.%s.po', Idioma, IdiomaDevuelto);
      MessageDlg('Título', 'Texto', mtInformation, [mbOk, mbCancel, mbYes], 0);
+
    MessageDlg('Título', 'Texto', mtInformation, [mbOk, mbCancel, mbYes], 0);
    end;</delphi>
+
end;</syntaxhighlight>
  
==Compiling po files into the executable==
+
==Compilando los archivos .po dentro del ejecutable==
  
If you don't want to install the .po files, but put all files of the application into the executable, use the following:
+
&nbsp;&nbsp;&nbsp;Si no deseas instalar los archivos .po, sino poner todos los archivos de la aplicación en el ejecutable, utiliza lo siguiente:
  
*Create a new unit (not a form!).
+
*Crea una nueva unidad (¡no un formulario!).
*Convert the .po file(s) to .lrs using tools/lazres:
+
*Convierte los archivos .po en .lrs utilizando la utilidad lazres del directorio tools:
<pre>
+
<pre> ./lazres unidad1.lrs unidad1.es.po</pre>
./lazres unit1.lrs unit1.de.po
+
&nbsp;&nbsp;&nbsp;Esto creará un archivo de inclusión unit1.lrs que empieza así
</pre>
+
<syntaxhighlight lang=pascal> LazarusResources.Add('unidad1.es','PO',[...</syntaxhighlight>
This will create an include file unit1.lrs beginning with
+
*Añade este código:
<pascal>
+
<syntaxhighlight lang=pascal> uses LResources, Translations;
LazarusResources.Add('unit1.de','PO',[
 
  ...
 
</pascal>
 
*Add the code:
 
<pascal>
 
uses LResources, Translations;
 
  
resourcestring
+
resourcestring
  MyCaption = 'Caption';
+
  MyCaption = 'Caption';
 +
 
 +
function TranslateUnitResourceStrings: boolean;
 +
var
 +
  r: TLResource;
 +
  POFile: TPOFile;
 +
begin
 +
  r:=LazarusResources.Find('unidad1.es','PO');
 +
  POFile:=TPOFile.Create;
 +
  try
 +
    POFile.ReadPOText(r.Value);
 +
    Result:=Translations.TranslateUnitResourceStrings('unidad1',POFile);
 +
  finally
 +
    POFile.Free;
 +
  end;
 +
end;
  
function TranslateUnitResourceStrings: boolean;
+
initialization
var
+
  {$I unidad1.lrs}</syntaxhighlight>
  r: TLResource;
 
  POFile: TPOFile;
 
begin
 
  r:=LazarusResources.Find('unit1.de','PO');
 
  POFile:=TPOFile.Create;
 
  try
 
    POFile.ReadPOText(r.Value);
 
    Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
 
  finally
 
    POFile.Free;
 
  end;
 
end;
 
  
initialization
+
*Llama a ''TranslateUnitResourceStrings'' al comienzo del programa. Puedes hacerlo en la sección de inicialización si quieres.
  {$I unit1.lrs}
 
  
</pascal>
+
==Método multiplataforma para determinar el idioma del sistema==
* Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.
 
  
==Trabajos previstos / ToDos==
+
&nbsp;&nbsp;&nbsp;Esta función suministra una cadena que representa el idioma de sistema de usuario. Funciona en Linux, Mac OS X y Windows.
 +
 
 +
<syntaxhighlight lang=pascal> uses
 +
Classes, SysUtils {añade aquí las unidades adionales que necesite tu código}
 +
{$IFDEF win32}
 +
  , Windows
 +
{$ELSE}
 +
  , Unix
 +
  {$IFDEF LCLCarbon}
 +
  , MacOSAll
 +
  {$ENDIF}
 +
{$ENDIF}
 +
  ;</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang=pascal> function GetOSLanguage: string;
 +
{Método multiplataforma para determinar el idioma del sistema}
 +
  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;</syntaxhighlight>
 +
 
 +
==Traducciendo el IDE==
 +
 
 +
===Archivos===
 +
&nbsp;&nbsp;&nbsp;Los archivos .po del IDE están en el directorio de fuentes de Lazarus:
 +
*lazarus/languages cadenas para el IDE
 +
*lcl/languages/ cadenas para la LCL
 +
*ideintf/languages/ cadenas para el interfaz del IDE
 +
 
 +
===Traductoress===
 +
*La traducción al alemán está a cargo de Joerg Braun.
 +
*La traducción al finés está a cargo de Seppo Suurtarla
 +
*La traducción al ruso está a cargo de Maxim Ganetsky
 +
 
 +
&nbsp;&nbsp;&nbsp;Si deseas iniciar una nueva traducción, pregunta en el correo si alguien ya está trabajando en ello.
 +
 
 +
&nbsp;&nbsp;&nbsp;Por favor, lee atentamente: [[Lazarus_Documentation#Translations/es|Traducciones]]
 +
 
 +
== Ver también ==
  
 
[[IDE_Development#Translations.2C_i18n.2C_lrt_files.2C_po_files|IDE Development: Translations, i18n, lrt, po files]]
 
[[IDE_Development#Translations.2C_i18n.2C_lrt_files.2C_po_files|IDE Development: Translations, i18n, lrt, po files]]
 +
 +
[[Getting_translation_strings_right|Getting translation strings right]]
 +
 +
[[Lazarus_Documentation#Translations|Translations]]

Latest revision as of 12:28, 1 March 2020

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

Descripción general sobre internacionalización (i18n)

   ¿Cómo hacer que los programas utilicen diferentes cadenas para diversos idiomas cómo inglés, chino, alemán, castellano, etc.?

Básicamente funciona de la siguiente forma: Añade un recurso de cadena (resourcestring) para cada literal, compila para obtener los archivos .rst y/o .po (el IDE lo hace automáticamente), crea un archivo .po para cada idioma (existen herramientas gráficas para ello) y utiliza las funciones de la unidad translations de la LCL para cargar el archivo adecuado al iniciar el programa.

Formatos de fechas, horas y números

   En Linux, BSD, Mac OS X hay diversas definiciones regionales/locales para definir el formato de la hora y de la fecha o del separador decimal. Para inicializar la RTL necesitas icluir la unidad clocale en la sección use de tu programa (archivo lpr).

Recursos de Cadena (Resourcestrings)

   Por ejemplo

  resourcestring
    Etiqueta1 = 'Algo de texto';
    HolaMundo1 = 'Hola Mundo';

   Son como constantes de cadena normales, lo que significa que se pueden asignar a cualquier cadena. Por ejemplo

  Label1.Caption := HolaMundo1;

   Al compilar se creará un archivo .rst por cada unidad, con el mismo nombre que esta, conteniendo los resursos de cadena (nombre y contenido)

Archivos .po

   Hay muchas herramientas gráficas libres (kbabel y poedit, por ejemplo) para editar archivos .po, que son texto simple al igual que los archivos .rst, pero con algunas opciones más, como una cabecera que proporciona los campos autor, la codificación, el idioma y fecha. Toda instalación de fpc proporciona la herramienta rstconv (windows: rstconv.exe). Esta herramienta se utiliza para convertir archivos .rst en archivos .po. El IDE realizar esta operación automáticamente.

   Ejemplo de uso de rstconv:

 rstconv -i unidad1.rst -o unidad1.po

Traduciendo

   Por cada idioma el archivo .po debe ser copiado y traducido. La unidad translation utiliza los códigos de idiomas habituales (en=inglés, de=alemán, es=español, it=italiano, ...) para buscarlos. Por ejemplo la traducción al alemán de unidad1.po se llamará unidad1.de.po. Por tanto hay que copiar el archivo unidad1.po en un archivo de nombre unidad1.it.po y así para todos los idiomas que queramos soportar y los traductores editaran el archivo .po específico de cada idioma.

   Nota para brasileños y portugueses: El IDE y la LCL de Lazarus únicamente tienen traducciones de portugués de Brasil y estos archivos se denominan con la extensión 'pb.po' y no 'pt.po'.

Opciones del IDE para la actualización automática de archivos .po

  • La unidad que contiene los recursos de cadena debe añadirse al paquete o proyecto.
  • Hay que proporcionar la ruta a .po si los archivos están en un directorio separado. Por ejemplo crear un directorio idiomas en el paquete/proyecto. Para proyectos ir a Proyecto > Opciones de proyecto, pestaña i18n. Para paquetes ir a Opciones > Integración en IDE.

   Cuando esta opción está activada, el IDE genera o actualiza el archivo .po base con la información contenida en los archivos .rst y .lrt (en cuyo caso la herramienta rstconv no será necesaria). El proceso de actualización se inicia mediante la recopilación de todas las entradas existentes en el .po base y en los archivos .rst y .lrt y luego aplica las siguientes características que encuentra y pone al día todo archivo .xx.po traducido.

Eliminación de entradas obsoletas

   Las entradas en el archivo .po base no se encontradas en el archivo en los archivos .rst y .lrt se eliminan, posteriormente, todas las entradas que se encuentran en archivos .xx.po traducidos que no se encuentran en el archivo .po base también se quitan. De esta manera los archivos .po se limpian de las entradas obsoletas y los traductores no tienen que traducir las entradas que no se utilizan.

Entradas duplicadas

   Las entradas duplicadas se producen cuando por alguna razón se utiliza el mismo texto para cadenas de recursos diferentes, un ejemplo, de esto es el archivo de Lazarus/ide/lazarusidestrconst.pas para la cadena 'Gutter';

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

   El archivo .rst de recursos para estas cadenas A converted .rst file for this resource strings tendría un aspecto similar a este en un archivo .po:

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

   Donde las líneas que comienzan con "#:" se consideran como los comentarios y las herramientas empleadas para traducir estas entradas verán las repetidas líneas msgstr "Gutter" como entradas duplicadas y provocarán errores o advertencias al cargar o guardar. Las entradas duplicadas se consideran eventualmente normales en archivos .po y necesitan tener algo de contexto asociado. La palabra clave msgctxt se utiliza para agregar contexto a las entradas duplicadas y la herramienta de actualización automática de utiliza la entrada ID (el texto que sigue a "#:" ) como el contexto, para el ejemplo anterior se produciría algo así como esto:

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

   En los archivos traducidos .xx.po la herramienta automática hace una comprobación adicional: si la entrada duplicada fue traducida ya, la nueva entrada toma la traducción previa, por lo que aparece como traducida automáticamente.

   La detección automática de duplicados aún no es perfecta, la detección se realiza cuando se agregan elementos a la lista y puede suceder que algunas entradas sin traducir se lean primero. Por lo tanto, puede llevar varias pasadas obtener todos los duplicados traducidos automáticamente por la herramienta.

Entradas confusas

   Los cambios en las cadenas de recursos afectan a las traducciones, por ejemplo cuando en un principio una cadena de recursos se define así:

 dlgEdColor = 'Syntax highlight';

   esto produce una entrada en .po similar a esta

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

   que si se han traducido al idioma castellano (este ejemplo está tomado de la historia de lazarus), puede resultar en

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

   Supongamos ahora que, en un momento posterior, la cadena de recursos se ha cambiado a

 dlgEdColor = 'Colors';

   la entrada en .po puede ser

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

   Tenga en cuenta que, si bien el ID sigue siendo igualmente lazarusidestrconsts.dlgedcolor la cadena ha cambiado de "Syntax highlight' a 'Colors', dado que la cadena se tradujo ya la traducción previa no puede coincidir con el nuevo significado. De hecho, para la nueva cadena probablemente 'Colores' puede ser una traducción mejor.    La herramienta de actualización automática avisa de esta situación y produce una entrada como esta:

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

   En los términos del formato de los archivos .po, el prefijo "#," significa que la entrada tiene una marca (confusa) y los programas traductores puede presentar al usuario un igu especial para este elemento, en este caso la marca significa que la traducción en su estado actual es dudosa y hay que revisarla con más cuidado por el traductor. El prefijo "#|" indica cuál fue la cadena anterior no traducida de esta entrada y le da al traductor una idea del porqué la entrada estaba marcada como confusa.

Traduciendo Formularios, Módulos de Datos y Marcos

   Cuando la opción i18n está activada para el proyecto/paquete el IDE crea automáticamente un archivo .lrt para cada formulario, cada vez que se guarda la unidad. Por tanto cuándo se habilita la opción por primera vez hay que abrir, modificar y guardar los formularios. Por ejemplo, si guardas un formulario unidad1 .pas el IDE crea el archivo unidad1.lrt. Y al compilar el IDE reúne todas las cadenas de todos los archivos .lrt y .rst en un único archivo .po, con el nombre del proyecto o del paquete, en el directorio i18n.

   Para los formularios que se traducen en tiempo de ejecución, hay que asignar un valor que apunte al achivo de traducción a LRSTranslator (definido en LResources) en la sección de inicio de una de las unidades del proyecto.

 ...
 uses
   ...
   LResources;
 ...
 ...
 initialization
   LRSTranslator:=TPoTranslator.Create('/ruta/al/archivo/po');

   La clase que traduce utilizando archivos .po, TPoTranslator, no está disponible en la LCL. Esta es una posible implementación (tomada en parte de DefaultTranslator.pas de la LCL) El código siguiente ya no se necesita si utilizas Lazarus 0.9.29. Sólo tienes que incluir DefaultTranslator en la cláusula 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.

   Alternativamente se puede transformar el archivo .po en una .mo, utilizando msgfmt, y utilizar la unidad DefaultTranslator.

 ...
 uses
 ...
 DefaultTranslator;

   con ello se buscará automáticamente en varios lugares preestablecidos un archivo .po (primero) o un archivo .mo; la desventaja es que hay que mantener ambos, los archivos .mo para la unidad DefaultTranslator y los archivos .po para TranslateUnitResourceStrings.

   Si utilizamos DefaultTranslator, se intentará detectar automáticamente la lengua basandose en la variable de entorno LANG (modificable utilizando la opción --lang de la línea de órdenes), buscando entonces en esos lugares la traducción (LANG representa la lengua deseada):

  • <Application Directory>/LANG/<Application Filename>.po (o .mo)
  • <Application Directory>/languages/LANG/<Application Filename>.po (o .mo)
  • <Application Directory>/locale/LANG/<Application Filename>.po (o .mo)
  • <Application Directory>/locale/LC_MESSAGES/LANG/<Application Filename>.po (o .mo)

   en sistemas *NIX esto se verá así

* /usr/share/locale/LANG/LC_MESSAGES/<Application Filename>.po (o .mo)

   también usará la parte corta del idioma (e.g. si es " es_ES" o " es_ES.UTF-8" y este no existe también probará con "es")

Seleccionando el idioma en el arranque el programa

   Para cada fichero .po hay que hacer una llamada a la función TranslateUnitResourceStrings de la unidad de translations de la LCL. Por ejemplo:

  {lo pimero: añadir las unidades "gettext" y "translations" en la cláusula uses}
 procedure TForm1.FormCreate(Sender: TObject);
  var
    PODirectorio, Idioma, IdiomaDevuelto: String;
  begin
    PODirectorio := '/ruta/a/lazarus/lcl/languages/';
    GetLanguageIDs(Idioma, IdiomaDevuelto); // en la unidad "gettext"
    TranslateUnitResourceStrings('LCLStrConsts', PODirectorio + 'lclstrconsts.%s.po', Idioma, IdiomaDevuelto);
    MessageDlg('Título', 'Texto', mtInformation, [mbOk, mbCancel, mbYes], 0);
 end;

Compilando los archivos .po dentro del ejecutable

   Si no deseas instalar los archivos .po, sino poner todos los archivos de la aplicación en el ejecutable, utiliza lo siguiente:

  • Crea una nueva unidad (¡no un formulario!).
  • Convierte los archivos .po en .lrs utilizando la utilidad lazres del directorio tools:
 ./lazres unidad1.lrs unidad1.es.po

   Esto creará un archivo de inclusión unit1.lrs que empieza así

 LazarusResources.Add('unidad1.es','PO',[...
  • Añade este código:
 uses LResources, Translations;

 resourcestring
   MyCaption = 'Caption';

 function TranslateUnitResourceStrings: boolean;
 var
   r: TLResource;
   POFile: TPOFile;
 begin
   r:=LazarusResources.Find('unidad1.es','PO');
   POFile:=TPOFile.Create;
   try
     POFile.ReadPOText(r.Value);
     Result:=Translations.TranslateUnitResourceStrings('unidad1',POFile);
   finally
     POFile.Free;
   end;
 end;

 initialization
   {$I unidad1.lrs}
  • Llama a TranslateUnitResourceStrings al comienzo del programa. Puedes hacerlo en la sección de inicialización si quieres.

Método multiplataforma para determinar el idioma del sistema

   Esta función suministra una cadena que representa el idioma de sistema de usuario. Funciona en Linux, Mac OS X y Windows.

 uses
 Classes, SysUtils {añade aquí las unidades adionales que necesite tu código}
 {$IFDEF win32}
  , Windows
 {$ELSE}
  , Unix
  {$IFDEF LCLCarbon}
   , MacOSAll
  {$ENDIF}
 {$ENDIF}
  ;
 function GetOSLanguage: string;
 {Método multiplataforma para determinar el idioma del sistema}
  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;

Traducciendo el IDE

Archivos

   Los archivos .po del IDE están en el directorio de fuentes de Lazarus:

  • lazarus/languages cadenas para el IDE
  • lcl/languages/ cadenas para la LCL
  • ideintf/languages/ cadenas para el interfaz del IDE

Traductoress

  • La traducción al alemán está a cargo de Joerg Braun.
  • La traducción al finés está a cargo de Seppo Suurtarla
  • La traducción al ruso está a cargo de Maxim Ganetsky

   Si deseas iniciar una nueva traducción, pregunta en el correo si alguien ya está trabajando en ello.

   Por favor, lee atentamente: Traducciones

Ver también

IDE Development: Translations, i18n, lrt, po files

Getting translation strings right

Translations