Archive for the ‘i18n’ Category

h1

Mehrsprachige Perlscripte

30. Juni 2008

Ein Perlscript, wie z.B. ein Plugin für Nagios zu schreiben, das in mehreren Sprachen seine Meldungen ausgibt, ist an sich einfach. Nur die Doku dazu ist eher mager und zu allererst steht man vor dem Problem, welches Modul man denn dafür am besten nehmen soll. Ich entschied mich dann für Locale::TextDomain – eine high-level Schnittstelle zu anderen Modulen.

Folgend ein kleines Beispiel:

#!/usr/bin/perl -w
use strict;
use warnings;
use lib "/opt/local/lib/perl5/site_perl/5.8.8";  # plattformspezifische Zeile
use Locale::TextDomain "en"; #  "./LocaleXData";
## einfaches Beispiel
my $translated = __"Hallo Welt!";
print "$translated\n";
## Anhängen eines Zeilenumbruchs (der zu übersetzende String muss in Klammern!)
print __("Mir geht's gut.") . "\n";
# Schön ist, dass man auch Umlaute verwenden kann -
# bei der Erstellung der po-Datei mit xgettext
# --out-format=UTF-8 angeben!
print __("Täglicher Unsinn mit scharfem ß.") . "\n";
## Variablen können auch übersetzt werden
my $msg = "Hallo Welt!";
print __$msg;
print "\n";
my $world = "Welt!";
print __"Hallo $world";
print "\n";

Dieses Script gibt folgende Ausgabe aus, wenn man ein paar Vorbereitungen getroffen hat:

$ export LANG=de_AT
$ ./world2.pl
Hallo Welt!
Mir geht's gut.
Täglicher Unsinn mit scharfem ß.
Hallo Welt!
Hallo Welt!

So und nun schalten wir auf English um:

$ export LANG=en_US
$ ./world2.pl
Hello World!
Mir geht's gut.
Daily nonsens with non-ASCII
Hello World!
Hello World!

Es wurden also die Texte „Hallo Welt!“ und „Täglicher Unsinn …“ übersetzt – letzterer etwas eigenwillig. Für „Mir geht’s gut.“ gabe s keine vor bereitet Übersetzung – daher wurde der deutsche Originaltext ausgegeben.

Die nötigen Vorarbeiten für dieses kleine „Wunder“ waren der Reihe nach:

  1. Installation von Locale::Textdomain mit cpan
  2. Anlegen eines Unterverzeichnisses LocaleData im Arbeitsverzeichnis des Scripts. Heisst das Verzeichnis anders, oder liegt es nicht in einem der @INC-Pfade muss es dediziert angegeben werden:
  3. use Locale::TextDomain "en",  "./mylocales";
  4. Export der zu übersetzenden Strings mit xgettext in ein po-File
  5. Übersetzeung dieser Strings (mit einem Editor) – der Text „Mir geht’s gut“ wurde dabei ausgelassen.
  6. Umwandlung des po in ein mo-File (binär) mittels msgfmt. Dieses ist exakt mit diesem Namen und Pfad LocaleData/<locale_name>/LC_MESSAGES/<localename>.mo abzulegen. Der locale-Name kommt also sowohl im Verzeichnis unterhalb von LocaleData als auch im Namen der Datei vor! Es kann allerdings auch en für en_US usw. verwendet werden.
  7. Im Script dann das Locale referenzieren. (Will man mehrere refrerenzieren, muss man das mit einer if-Abfrage machen.)

ABER: So ganz komplett ist die Sache nicht, denn vor allem die Mischung aus Fixtext und Variablen kann man man so nicht sauber übersetzen. Wie das nun geht sieht man am besten in diesem Beispiel:

#!/usr/bin/perl -w

use strict;
use warnings;

use lib "/opt/local/lib/perl5/site_perl/5.8.8";  # plattformspezifische Zeile
use Locale::TextDomain "en"; #  "./LocaleXData";

## einfaches Beispiel

my $translated = __"Hallo Welt!";
print "$translated\n";

## Anhängen eines Zeilenumbruchs (der zu übersetzende String muss in Klammern!)

print __("Mir geht's gut.") . "\n";

# Schön ist, dass man auch Umlaute verwenden kann - 
# bei der Erstellung der po-Datei mit xgettext 
# --out-format=UTF-8 angeben!
print __("Täglicher Unsinn mit scharfem ß.") . "\n";

## Variablen können auch übersetzt werden

my $msg = "Hallo Welt!";
print __$msg;
print "\n";

# Schwieriger wird es bei Variablen, die gemeinsam mit Fixtext ausgegeben
# werden sollen. Das folgende Beispiel funktioniert mehr oder weniger zufällig,
# und nur hier, da "Hallo Welt!" oben schon übersetzt wurde. Aber xgettext 
# würde sich dagegen sträuben ("world2.pl:37: ungültige Interpolation einer 
# Variablen bei »$«") 
my $world = "Welt!";
#print __"Hallo $world";  # muss für parsing mit xgettext auskomentiert werden
						  # danach könnte man es wieder aktivieren - Perl käme damit zurecht
						  # aber Lösung ist das natürlich keine ...
print "\n";

# Richtig ist die Mischung von Fixtext und Variablen so:
print __x ("Hallo {person}!\n",
	person => "Franz");

Dieses Script kann man nun so parsen:

$ xgettext world3.pl --from-code=UTF-8 --keyword=__ --keyword=__x

Und bekommt dann ein po-file, das man so übersetzt:

#: world3.pl:12
msgid "Hallo Welt!"
msgstr "Hello World!"

#: world3.pl:18
msgid "Mir geht's gut."
msgstr "I am fine."

#: world3.pl:23
msgid "Täglicher Unsinn mit scharfem ß."
msgstr "Daily nonsens with non-ASCII."

#: world3.pl:44
#, perl-brace-format
msgid "Hallo {person}!\n"
msgstr "Hello {person}!\n"

Und es dann ganz einfach in ein mo umwandelt und dieses wie oben schon beschrieben ablegt.

Und Die Ausgabe schaut dann so aus:

$ ./world3.pl 
Hallo Welt!
Mir geht's gut.
Täglicher Unsinn mit scharfem ß.
Hallo Welt!

Hallo Franz!

$ export LANG=en_US

$ ./world3.pl 
Hello World!
I am fine.
Daily nonsens with non-ASCII.
Hello World!

Hello Franz!
Advertisements