Bash-Skripte in mehrere Sprachen übersetzen

Babylonische Schriften

Auf Mehrsprachigkeit muss man auch bei Bash-Skripten nicht verzichten, die Internationalisierung von Skripten ist mit den richtigen Werkzeugen recht einfach.

Es muss nicht immer eine Hochsprache sein, viele nützliche Programme und Werkzeuge wie zum Beispiel der Hot-Plug-Daemon sind "einfache" Bash-Skripte. Oftmals beginnt es mit wenigen Zeilen, die ein kleines Problem lösen und nur vom Programmierer selbst benutzt werden. Wird so ein Skript doch veröffentlicht, unterstützt es fast immer nur eine Sprache. Selbst deutsche Programmierer bevorzugen dafür Englisch – mit dem Ergebnis, dass es deutsche Anwender unnötig schwer haben, das teils gebrochene Englisch wieder zurück zu übersetzen.

Dabei lassen sich Bash-Skripte relativ einfach mit mehreren Sprachen ausrüsten, für den Programmierer bedeutet das nur unwesentlichen Zusatzaufwand. Besser bekannt ist die Internationalisierung unter dem Kürzel "i18n", das ist die Kurzform für "internationalisation"; das Wort beginnt mit "i", dann folgen 18 Buchstaben und am Ende ein "n".

Texte kennzeichnen

Bei Bash-Skripten wird einfach jeder Text, der bei der Übersetzung an andere Sprachen angepasst werden muss, mit einem Dollar-Zeichen markiert. Anschließend werden alle zu übersetzenden Texte mit dem Befehl bash --dump-po-strings aus dem Skript heraus gelöst. Das Programm xgettext aus dem Paket gettext schreibt die Texte dann in eine separate Datei, die als Ausgangsbasis aller Übersetzungen dient. Das komplette Kommando für das Skript hwdetect lautet:

bash --dump-po-strings hwdetect | xgettext -L PO -o po/hwdetect.pot -

Das Unterverzeichnis po muss vorher angelegt werden. Die Datei hwdetect.pot dient als Ausgangsbasis für alle Übersetzungen. Sie enthält bereits die Ursprungstexte sowie Platzhalter für die Übersetzung.

Sprache hinzufügen

Soll eine neue Sprache hinzugefügt werden, etwa Deutsch, geht das mit dem Kommando msginit -l de_DE. Das Programm sucht nach einer Datei mit der Endung .pot im aktuellen Verzeichnis, fragt nach der E-Mail-Adresse des Übersetzers und legt in diesem Fall die Datei de.po an. Die Sprachdatei unterscheidet sich nur im Dateikopf von der Ursprungsdatei, der Übersetzer muss also weder die Skriptsprache beherrschen noch programmieren können. Die vier Zeilen

if [ ! -d /proc/bus/pci ]; then
  echo $"/proc/bus/pci does not exist, no PCI bus detected. Abort."
  exit 1
fi

im Skript, das mehrsprachig werden soll, führen in der Datei de.po zu diesem Eintrag:

#: hwdetect:16
msgid "/proc/bus/pci does not exist, no PCI bus detected. Abort."
msgstr ""

In der ersten Zeile stehen der Programmname sowie die Zeilennummer, wo der Ausgangstext zu finden ist. Der Text selbst steht in der zweiten Zeile als msgid, der Übersetzer trägt dann den deutschen Text in der dritten Zeile hinter msgstr ein:

#: hwdetect:16
msgid "/proc/bus/pci does not exist, no PCI bus detected. Abort."
msgstr "/proc/bus/pci existiert nicht, keinen PCI-Bus gefunden. Abbruch."

Jede Zeile darf standardmäßig maximal 80 Zeichen lang sein, bei längeren Texten sieht die Formatierung etwas anders aus:

#: hwdetect:16
msgid "/proc/bus/pci does not exist, no PCI bus detected. Abort."
msgstr ""
"Das Verzeichnis /proc/bus/pci existiert nicht.\\n Mögliche Ursachen sind, "
"dass entweder das Proc-Dateisystem nicht eingebunden wurde oder der "
"Rechner keinen PCI-Bus besitzt.\\nDas Programm kann nicht fortgesetzt werden."

Der deutsche Text wird im letzten Beispiel an zwei Stellen umgebrochen. Dafür ist das Zeichen "\n" dafür zuständig, es muss jedoch mit einem weiteren Backslash geschützt werden, da der Backslash ein Steuerzeichen ist.

Die Übersetzung kann auch teilweise erfolgen – an Stellen, wo der übersetzte Text fehlt, bekommt der Benutzer später die Originalfassung angezeigt. Es ist natürlich unschön, wenn mittendrin die Sprache wechselt, besser als überhaupt keine Sprachanpassung ist dies aber allemal.

Die Möglichkeit unvollständiger Übersetzungen erlaubt es auch, bei Erweiterungen des Programms zunächst die alte Übersetzung weiter zu verwenden, bis ein Übersetzer auch die neuen Texte anliefert.

Änderungen nachziehen

Die Funktionsweise ist einfach: Die Bash stellt beim Aufruf anhand der Umgebungsvariablen LANG fest, welche Sprache gewünscht wird, und lädt die entsprechende Sprachdatei. Dann wird nach der ursprünglichen Zeichenkette in der Übersetzungsdatei gesucht und, falls gefunden, die Übersetzung angezeigt. Das bedeutet aber auch, dass alle Übersetzungsdateien angepasst werden müssen, wenn im Skript Texte verändert werden.

Zum Glück gibt es msgmerge. Nachdem, wie gehabt, die pot-Datei neu erzeugt wurde, macht das Programm einen Abgleich zwischen den alten Übersetzungen und der neuen Ursprungsdatei. In gewissen Grenzen klappt die Zuordnung automatisch, so dass etwa Rechtschreibfehler oder wenige Worte problemlos ausgetauscht werden können. Sieht man sich die Datei später mit einem Editor mit Syntax-Highlighting (etwa dem Midnight Commander) an, erscheinen fehlende Übersetzungstexte und möglicherweise ungenaue Übersetzungen in rot.

Nach der Übersetzung müssen die Textdateien noch in ein Binärformat umgewandelt werden. Dies erledigt das Kommando msgfmt -o hwdetect.mo de.po. Für den Betrieb wird lediglich die mo-Datei benötigt, für jede Sprache gibt es eine eigene.

Im Skript legen Sie mit den Umgebungsvariablen TEXTDOMAIN und TEXTDOMAINDIR fest, wo und unter welchem Namen die mo-Dateien abgelegt wurden. In der Variablen TEXTDOMAIN steht der Präfix der mo-Datei – der Programmname – und sie muss in jedem Fall angegeben werden.

TEXTDOMAINDIR enthält einen Teil des Pfades zur mo-Datei. Liegt die deutsche mo-Datei etwa im Unterverzeichnis locale/de/LC_MESSAGES, muss locale in TEXTDOMAINDIR angegeben werden. Die Angabe kann entfallen, wenn die mo-Datei unter /usr/share/locale einsortiert wurde.

Abläufe automatisieren

Die Vorgehensweise erscheint auf den ersten Blick ziemlich kompliziert – nach jeder Programmänderung müssen die Texte neu extrahiert, dann alle Übersetzungen aktualisiert und schließlich für jede Sprache eine neue mo-Datei erzeugt werden. Diese Aufgaben lassen sich jedoch problemlos automatisieren.

Dazu benutzt man das Programm make, das vornehmlich zum Kompilieren von Programmen verwendet wird. Beim Aufruf liest make die Datei Makefile oder makefile im aktuellen Verzeichnis und führt die dort beschriebenen Aktionen aus. Auf der Heft-CD finden Sie im Verzeichnis LinuxUser/i18n das Makefile aus Listing 1, bei dem Sie lediglich den Namen des Programms in der dritten Zeile anzupassen brauchen. Die Ursprungssprachdatei wird im Unterverzeichnis po abgelegt, das auch alle Übersetzungen enthalten muss. Die mo-Dateien werden im Unterverzeichnis locale entsprechend einsortiert, im Skript muss deshalb TEXTDOMAINDIR=locale gesetzt sein. Mit make install werden die mo-Dateien automatisch in /usr/share/locale einsortiert, wozu Root-Rechte erforderlich sind, Sie brauchen dann nur noch die Variable TEXTDOMAINDIR aus dem Skript zu entfernen.

Listing 1

Makefile

01 # Generierung aller nötigen i18n-Sprachdateien
02
03 PROGNAME=hwdetect
04
05 all: po/$(PROGNAME).pot PO MO
06
07 po/$(PROGNAME).pot:     $(PROGNAME)
08         mkdir -p po
09         bash --dump-po-strings $(PROGNAME) | xgettext -L PO -o po/$(PROGNAME).pot -
10
11 PO:
12         for d in `ls -1 po/*.po 2>/dev/null`; do \
13           mv $$d $${d}~; \
14           msgmerge -qo $$d $${d}~ po/$(PROGNAME).pot; \
15         done
16
17 MO:
18         mkdir -p locale/de/LC_MESSAGES
19         for d in `cd po; ls -1 *.po 2>/dev/null`; do \
20           msgfmt -o locale/$${d%.po}/LC_MESSAGES/$(PROGNAME).mo po/$$d; \
21         done
22
23 install:        all
24         for d in `cd locale; ls -1d * 2>/dev/null`; do \
25           install -d /usr/share/locale/$$d/LC_MESSAGES; \
26           install locale/$${d}/LC_MESSAGES/$(PROGNAME).mo /usr/share/locale/$$d/LC_MESSAGES; \
27         done
28
29 clean:
30         rm -fR locale po/$(PROGNAME).pot po/*.po~

Listing 2 zeigt ein Beispielprogamm, bei dem alle Bildschirmausgaben für die Übersetzung vorbereitet sind. Nicht übersetzt werden hingegen funktionelle Zeichenketten, wie zum Beispiel die Suchmuster bei grep in den Zeilen 44, 45 und 51.

Listing 2

hwdetect

01 #!/bin/bash
02
03 PCIIDS=pci.db
04 PCIMAP=/lib/modules/`uname -r`/modules.pcimap
05
06 TEXTDOMAIN=hwdetect
07 TEXTDOMAINDIR=locale
08
09 # Tabulator-Zeichen als Variable, ist so leichter lesbar
10 Tab=$'\t'
11
12 #
13 # Wir benötigen Zugriff auf /proc/bus/pci, daher prüfen, ob es das Verzeichnis
14 # überhaupt gibt
15 if [ ! -d /proc/bus/pci ]; then
16   echo $"/proc/bus/pci does not exist, no PCI bus detected. Abort."
17   exit 1
18 fi
19
20 #
21 # Die Pfade zur PCI-ID-Datenbank (http://pciids.sf.net/pci.db) und
22 # PCI-Modulliste (/lib/modules/[Kernel-Version]/modules.pcimap) prüfen
23 if [ ! -r "$PCIIDS" -o ! -r "$PCIMAP" ]; then
24   echo $"PCI ID data base or PCI modules list not found. Abort."
25   exit 1
26 fi
27
28 #
29 # /proc/bus/pci/??/* nach Geräten scannen
30 for device in /proc/bus/pci/??/*; do
31   # Vendor-ID, Device-ID und Class-ID auslesen
32   VendorID=`hexdump -s 0 -n 2 -e '1/2 "%04x"' $device`
33   DeviceID=`hexdump -s 2 -n 2 -e '1/2 "%04x"' $device`
34
35   #
36   # Class-ID: Teilt die Hardware in verschiedene Gruppen, 0x0200 sind
37   # zum Beispiel Netzwerkkarten. Nachzulesen bei
38   #   http://www.pcidatabase.com/pci_c_header.php
39   # in der Struktur "PciClassCodeTable"
40   ClassID=`hexdump -s 10 -n 2 -e '1/2 "%04x"' $device`
41
42   #
43   # Hersteller und Gerätebezeichnung aus der PCI-ID-Datenbank auslesen
44   VendorName=`grep "^v${Tab}${VendorID}${Tab}.*${Tab}0" $PCIIDS | cut -d"${Tab}" -f3`
45   DeviceName=`grep "^d${Tab}${VendorID}${DeviceID}${Tab}.*${Tab}0" $PCIIDS | cut -d"${Tab}" -f3`
46   echo $"Vendor: $VendorName [$VendorID]"
47   echo $"Device: $DeviceName [$DeviceID]"
48
49   #
50   # Bezeichnung des Kernel-Moduls aus der PCI-Modulliste auslesen
51   ModuleName=`grep "^.* *0x0000${VendorID} 0x0000${DeviceID}" $PCIMAP | cut -d" " -f1 | xargs`
52   set – $ModuleName
53
54   if [ "$#" -eq 0 ]; then
55     echo -e $"No matching module available in the current kernel.\n"
56   elif [ "$#" -eq 1 ]; then
57     echo -e $"Responsible kernel module: $ModuleName\n"
58   else
59     echo -e $"Responsible kernel modules: $ModuleName\n"
60   fi
61 done

Anders als die Kommentare sind Bildschirmausgaben in Listing 2 immer Englisch. Sie könnten auch Deutsch sein, jedoch verkraftet das Konvertierungsprogramm xgettext derzeit keine deutschen Umlaute und Sonderzeichen. In der Übersetzung hingegen sind Umlaute kein Problem.

Ausführliche Texte

Viel wichtiger ist noch: Sehr viele Leute beherrschen neben ihrer Muttersprache auch Englisch. Der Übersetzer bekommt später die Texte aus dem Skript als Vorlagen – bei deutschen Ausgangstexten ist die Menge der potenziellen Übersetzer deutlich kleiner als bei englischen. Auch beim Formulieren der Texte sollte man dies im Hinterkopf haben: Der Übersetzer muss den Sinn des Textes erkennen können, um ihn korrekt zu übersetzen. Hier ein Beispiel:

if [ ! -d /proc/bus/pci ]; then
  echo $"Missing: " $i
  exit 1
fi

Die POT-Datei enthält später nur das Wort "Missing":

#: hwdetect:16
msgid "Missing: "
msgstr ""

Der Übersetzer hat also keine Chance, zu erkennen, ob hier eine oder mehrere Dateien, ein Verzeichnis oder nur eine Benutzereingabe fehlt. Auch muss vermieden werden, dass die Ausgabe zerstückelt wird:

echo $"File" $File $"contains" `wc -l text` "lines" and `wc -c text` $"bytes"

Der Übersetzer bekommt nur die Fragmente zu sehen:

msgid "File"
msgstr ""
msgid "contains"
msgstr ""
msgid "bytes"
msgstr ""

Auch hier ist eine sinnvolle Übersetzung von vornherein zum Scheitern verurteilt. Besser ist es, die Zahl der Zeilen und Zeichen zunächst in einer Variablen zu speichern:

Lines=`wc -l text`
Characters=`wc -c text`
echo $"File $File contains $Lines lines and $Characters bytes"

Der Übersetzer muss dann lediglich die Variablennamen eins zu eins in die Übersetzung einbauen:

msgid "File $File contains $Lines lines and $Characters bytes"
msgstr "Die Datei $File enthält $Lines Zeilen und $Characters Zeichen"

Das Beispielprogramm aus Listing 2, das Sie ebenfalls auf der Heft-CD finden, ist eine einfache Hardware-Erkennung. Es zeigt Hersteller, Gerätebezeichnung und zugehöriges Kernel-Modul an – sofern das Gerät vom aktuellen Kernel unterstützt wird. Mit wenigen Änderungen kann man so zum Beispiel die Kernel-Module aller erkannten Geräte oder bestimmter Geräteklassen nachladen lassen. Für den Betrieb ist noch die Datei pci.db von [1] erforderlich, sie enthält die Hersteller- und Gerätebezeichnungen.

Infos

[1] Geräte- und Herstellerbezeichnungen: http://pciids.sf.net/pci.db

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Hardware-Erkennung per Bash-Skript
    Die automatische Hardware-Erkennung beherrschen die meisten Distributionen erst seit wenigen Jahren. Dem Benutzer erscheint sie oft mystisch und magisch. Dabei kann jeder mit wenigen Zeilen Bash-Code eine eigene Hardware-Erkennung programmieren.
  • Programme übersetzen
    Für das Sprachgewirr auf dem Linux-Desktop gibt es eine Lösung: selbst Programme übersetzen. Damit ist sogar ein KDE "op Platt" kein Problem mehr.
  • Ohne Crash durch die Bash
    Mit der Bourne-Again-Shell schreiben Linux-Anwender Skripte, die nervige und stupide Aufgaben automatisieren – etwa um auf dem Notebook Strom einzusparen.
  • Grafische Werkzeuge zum Lokalisieren im Vergleich
    Damit eine Software in der Landessprache erscheint, muss Sie jemand übersetzen. Leistungsfähige Tools machen es jedermann leicht, einen ganz persönlichen Beitrag zur Open-Source-Welt zu leisten.
  • Volle Kontrolle
    Arch Linux gibt Ihnen mehr als nur die Möglichkeit, unter die Motorhaube des Systems zu schauen: Sie sitzen Sie selbst am Steuer und fahren ohne Autopilot.
Kommentare

Infos zur Publikation

LU 10/2016: Kryptographie

Digitale Ausgabe: Preis € 0,00
(inkl. 19% MwSt.)

LinuxUser erscheint monatlich und kostet 5,95 Euro (mit DVD 8,50 Euro). Weitere Infos zum Heft finden Sie auf der Homepage.

Das Jahresabo kostet ab 86,70 Euro. Details dazu finden Sie im Computec-Shop. Im Probeabo erhalten Sie zudem drei Ausgaben zum reduzierten Preis.

Bei Google Play finden Sie digitale Ausgaben für Tablet & Smartphone.

HINWEIS ZU PAYPAL: Die Zahlung ist ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!

Aktuelle Fragen

Probleme mit MPC/MPD
Matthias Göhlen, 27.09.2016 13:39, 0 Antworten
Habe gerade mein erstes Raspi Projekt angefangen, typisches Einsteigerding: Vom Raspi 3B zum Radi...
Soundkarte wird erkannt, aber kein Ton
H A, 25.09.2016 01:37, 6 Antworten
Hallo, Ich weiß, dass es zu diesem Thema sehr oft Fragen gestellt wurden. Aber da ich ein Linu...
Scannen nur schwarz-weiß möglich
Werner Hahn, 20.09.2016 13:21, 2 Antworten
Canon Pixma MG5450S, Dell Latitude E6510, Betriebssyteme Ubuntu 16.04 und Windows 7. Der Canon-D...
Meteorit NB-7 startet nicht
Thomas Helbig, 13.09.2016 02:03, 4 Antworten
Verehrte Community Ich habe vor Kurzem einen Netbook-Oldie geschenkt bekommen. Beim Start ersch...
windows bootloader bei instalation gelöscht
markus Schneider, 12.09.2016 23:03, 1 Antworten
Hallo alle zusammen, ich habe neben meinem Windows 10 ein SL 7.2 Linux installiert und musste...