Mittels dem Tool gettext können Programme für den internationalen Gebrauch vorbereitet werden.
Das Tool gibt es bei rubyforge und kann als gem installiert werden:
gem -install gettext
Eine Anleitung findet sich bei http://www.yotabanana.com/hiki/ruby-gettext-howto.html ich habe hier eine kurze Zusammenfassung meines Test erstellt.
Zur Klärung verschiedener Begriffe dieser Anleitung:
- LOCALE: Definition der verwendeten Lokalisierung (Sprache, Darstellung der Ziffern, Datum...) Mit Locale.current kann man die aktuell verwendete LOCALE ermitteln.
- pot: Dateiendung (Portable Object File Template)
- po: Dateiendung (Portable Object File)
- mo: Dateiendung (Machine Object File)
Grundsätzliche Struktur
- Alle sprachabhängigen Textausgaben (puts, Variablenzuweisung...) werden im Skript mit der Methode _() definiert.
Beispiel: puts _("Hello World") - Das Programm rgettext extrahiert die Texte und generiert eine pot-Datei
- Die pot-Datei wird als Vorlage für die Übersetzungen verwendet (po-Dateien)
- Das Programm rmsgfmt generiert aus den po-Dateien mo-Dateien
- Wird eine mo-Datei für das aktuelle Locale gefunden, werden diese Texte verwendet.
- Der Speicherort der mo-Datei muss dem System bekannt sein. Details siehe Suchpfad für Übersetzungen (mo-Datei)
Anmerkungen:
- Die Texte werden zum zeitpunkt des bindings festgelegt. Siehe Sprachwechsel für Details.
Erster Testlauf
Schritt eins: Programm anlegen
require 'gettext' class Test include GetText bindtextdomain("test") bindtextdomain("test", :path => '.') #~ bindtextdomain('test', :path => "#{File.dirname(__FILE__)}") def say_hello() puts _("Hello World") end end
Alle Ausgaben müssen für die Benutzung mit gettext vorbereitet werden. Dazu werden alle Textausgaben (puts, Variablenzuweisung...) mit der Methode _ umgeben (im Beispiel in Methode say_hello).
bindtextdomain sorgt dabei dafür, das die Texte vorhanden sind.
Schritt 2: Texte extrahieren und übersetzen
Annahme: Obiges Programm hat den Namen test_gettext.
rgettext test_gettext.rb -o test.pot
Das Programm rgettext erzeugt eine pot-Datei (hier test.pot).
Um eine deutschsprachige Unterstützung des Programms zu erstellen kopiert man diese pot-Datei als test.po in einen Ordner de.
Deutsche Übersetzung erstellen
Die gerade erstellte de/test.po-Datei in einem Editor öffnen und bei
#: test_gettext.rb:20 msgid "Hello World" msgstr ""
in die Zeile mit msgstr die Übersetzung eintragen:
#: test_gettext.rb:20 msgid "Hello World" msgstr "Hallo Welt"
Der Text Hello World als Parameter der Funktion _() wird bei einem deutschen LOCALE in Hallo Welt übersetzt.
Übersetzung generieren.
Die Übersetzung in der po-Datei muss in eine mo-Datei übersetzt werden. Dazu dient das Programm rmsgfmt.
rmsgfmt de/test.po -o de/test.mo
Test
Danach kann das Programm erneut getestet werden. Auf einem deutschen Systemn sollte jetzt ein deutscher Text erscheinen.
Hinweise
- rgettext und rmsgfmt erzeugen keine Dateien, wenn diese schon vorhanden sind.
- Ist keine Sprachdefinition für das aktuelle Locale (Locale.current) vorhanden, wird der Text in _ verwendet.
- Das Locale ist auch im Programm umstellbar Locale.current =
- Es gibt ein Tool poedit mit dem die po-Dateien bearbeitet werden können.
Poedit - Ein Editor zum Übersetzen
Das Tool poedit erlaubt eine einfache Bearbeitung von po-Dateien.
Insbesonders können neue po-Dateien aus pot-Dateien angelegt werden und vor allem schon vorhandene po-Dateien können aus pot-Dateien aktualisiert werden. Dabei bleiben Übersetzungen erhalten, neue Texte werden eingefügt und die Referenzen zuu den Source-Code-Zeilen werden aktualisiert.
Suchpfad für Übersetzungen (mo-Datei)
Die m5o-Datei wird in vordefinierten Systempfaden gesucht.
Unter Windows und dem One-Click Installer werden die Sprachdateien in einem der folgenden Verzeichnisse erwartet:
C:/Program Files/ruby/share/locale/DE/LC_MESSAGES/ C:/Program Files/ruby/share/locale/de/LC_MESSAGES/ C:/Program Files/ruby/local/share/locale/DE/LC_MESSAGES/ C:/Program Files/ruby/local/share/locale/de/LC_MESSAGES/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/data/locale/DE/LC_MESSAGES/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/data/locale/de/LC_MESSAGES/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/data/locale/DE/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/data/locale/de/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/locale/DE/ C:/Program Files/ruby/lib/ruby/gems/1.8/gems/gettext-1.90.0/locale/de/
Ich schlage davon abweichend folgende Struktur vor:
test_gettext test.pot de/test.po de/test.mo en/test.po en/test.mo
oder
test_gettext test.pot locale/de/test.po locale/de/test.mo locale/en/test.po locale/en/test.mo
Soll so ein eigener Speicherort (z.B. beim Programm) verwendet werden, kann bindtextdomain den Pfad im Parameter :path definiert werden.
bindtextdomain("test", :path => '.')
Noch zu testen: Wie verhält sich der Suchpfad wenn das Hauptprogramm in anderen Ordnern liegt. Ist :path relativ zum Hauptprogramm oder zur Source-Datei?
Evtl. besser:
bindtextdomain('test', :path => "#{File.dirname(__FILE__)}")
Texte mit Parametern
Zum Übersetzen mit Parametern gibt es prinzipiell zwei Möglichkeiten:
Stellungsparameter mit %
def say_summe( p1, p2 ) puts _("%1i and %1i give %i") % [ p1, p2, p1+p2 ] #de: %1i und %1i ergeben %i end
Vorteil:
- kompakt Nachteil:
- Reihenfolge in der Ausgabe ist vordefiniert → Das ist für mich ein Argument diese Variante nicht zu nutzen, nicht jede Sprache hat den gleichen Satzbau.
Parameter als Hash
def say_summe( p1, p2 ) puts _("%{zahl1} and %{zahl2} give %{summe}") % { :zahl1 => p1, :zahl2 => p2, :summe => p1+p2 } #de: %{zahl1} und %{zahl2} ergeben immer noch %{summe} end
Vorteil:
- Übersetzer kann anhand des Parameterschlüssels den Sinn des Parameters erkennen.
- Variablenreihenfolge beliebig (nicht jede Sprache hat den gleichen Satzbau).
- Variable kann mehrfach im Text verwendet werden.
Nachteil:
- Längeres Coding
Sprachwechsel
Wird die Sprache im laufenden Programm gewechselt, muss das Binding erneuert werden.
Soll eine bestimmte Sprache eingestellt werden sollte es zum Programmanfang stattfinden.
class Test include GetText bindtextdomain("test") def say_hello puts _("Hello World") end end Test.new.say_hello # -> Hallo Welt #Sprache umsetzen Locale.current = 'fr_fr' #Nach dem Umsetzen des locales muss das binding erneuert werden. class Test bindtextdomain("test", :path => '!!locale') end Test.new.say_hello #-> Bonjour tout le monde
Tipp
Die Klassen (oder Objecte...) sollten die Möglichkeit eines Sprachwechsel enthalten.
class Test include GetText def self#set_language() bindtextdomain("test", :locale => language.to_str ) end set_language( Locale.current ) #initialisieren def say_hello puts _("Hello World") end end #... Test.set_language('fr_fr') #...
Evtl. macht es Sinn, ein Ereignis Sprachwechsel zu definieren und zu abonnieren.