=head1 Internationalisierungs-Framework auswählen =head2 Autor Steffen Winkler C<> =head2 Bio Seit 1960 gibt es mich. Ich programmiere Perl seit Ende 2000, erst privat und dann auch beruflich. Zur Zeit bin ich bei der SIEMENS AG in Erlangen beschäftigt. Dort arbeite ich vorwiegend im Bereich der Webprogrammierung. Den Deutschen Perlworkshop besuche ich seit 2003. =head2 Abstract Warum Locale::TextDomain, obwohl viele Frameworks im CPAN Locale::Maketext benutzen? Im Anschluss an meinen Vortrag DBD::PO in Frankfurt/Main gab es eine rege Diskussion, sowohl in Frankfurt als auch bei Erlangen-PM. Es gibt im CPAN 2 Internationalisierungs-Frameworks, Locale::TextDomain (Perl Interface to Uniforum Message Translation) und Locale::Maketext (framework for localization). Was sind die Unterschiede? Wo sind die Grenzen? =head2 Über was ich heute sprechen möchte. Vom Quelltext bis zur mehrsprachigen Anwendung auf 2 Wegen. Egal welches Internationalisierungs-Framework man vom CPAN benutzt, man muss mit Einschränkungen leben. Bei guter Wahl sind diese sehr gering. =head2 Am Anfang ist der Quelltext der Anwendung. print 'You can log out here.'; printf 'He lives in %s, %s.', $town, $address; printf '%d people live here.', $people; printf 'These are %d books.', $books; printf 'He has %s houses in %s, %s.', $houses, $town, $address; printf '%s books are in %s shelves.', $books, shelves; =head2 PO-Files - Was ist das? PO ist die Abkürzung für "portable object". GNU gettext PO-Files kann man benutzen, um Programme mehrsprachig zu machen. Im File stehen neben dem Originaltext und der Übersetzung verschiedene Kommentare und Flags. MO-Files sind die Binärvariante von PO-Files. =head2 auf Locale::Maketext::Simple umschreiben Verwendet wird dabei das Basismodul Locale::Maketext und ein Modul, welches gettext PO/MO-Files einliest. Das ist Locale::Maketext::Lexicon::Gettext. Locale::Maketext::Simple exportiert die Funktion "loc". [_n] mit n = 1, 2, ... ist die generelle Schreibweise für Platzhalter. In den [] kann ein Funktionsname vorangestellt werden, nachgestellt die Parameter. Das Trennzeichen ist das ",". "quant", kurz "*", ist der Funktionsname für Pluralverarbeitung. print loc('You can log out here.'); print loc( 'He lives in [_1], [_2].', $town, $address, ); print loc( '[quant,_1,person lives,people live] here.', $people, ); Ich habe keine Idee, wie man nachfolgende Phrase mit "quant" schreiben soll. Mit "quant" schreibt man so etwas wie Wert und nachfolgender Maßeinheit. Hier beginnt die Pluralform aber schon vor dem Wert. Das Problem ist, "quant" verlangt das Weglassen von "_1" in den Pluralformen und auch das Weglassen des darauf folgenden Leerzeichens. print loc( '[myplural,_1,It is _1 book,These are _1 books].', # ???????? ^^^^^ ??? ^^^^^^^^^ ??? $books, ); print loc( 'He has [quant,_1,house,houses] in [_2], [_3].', $houses, $town, $address, ); print loc( '[quant,_1,book is,books are] in [*,_2,shelf,shelves].', $books, $shelves, ); =head2 auf Locale::TextDomain umschreiben Locale::TextDomain gehört zur Distribution libintl-perl. Es gibt mehrere exportierte Funktionen. Der Funktionsname ist einfach gebaut. x steht für Platzhalter, n für Pluralform und p für Kontext. Die Parameterreihenfolge ist, wenn vorhanden: Kontext, Singular, Plural, Anzahl für Pluralauswahl und dann der Hash mit den Platzhalterdaten. Nicht alle Varianten aus n, p und x sind implementiert. Wenn man x auch ohne Platzhalter benutzt und sich an die alphabetische Reihenfolge hält, bleiben __x, __nx, __px und __npx übrig. __('msgid') __x( 'msgid', name1 => $value1, name2 => $value2, ... ) __n('msgid', 'msgid_plural', $count) __nx( 'msgid', 'msgid_plural', $count, name1 => $value1, name2 => $value2, ... ) __xn( 'msgid', 'msgid_plural', $count, name1 => $value1, name2 => $value2, ... ) __p('context', 'msgid') __px( 'context', 'msgid', name1 => $value1, name2 => $value2, ... ) __np('context', 'msgid', 'msgid_plural', $count) __npx( 'context', 'msgid', 'msgid_plural', $count, name1 => $value1, name2 => $value2, ... ) print __('You can log out here.'); print __x( 'He lives in {town}, {address}.', town => $town, address => $address, ); print __nx( '{num} person lives here.', '{num} people live here.', $people, num => $people, ); print __nx( 'It is {num} book.', 'These are {num} books.', $books, num => $books, ); print __nx( 'He has {num} house in {town}, {address}.', 'He has {num} houses in {town}, {address}.', $houses, num => $houses, town => $town, address => $address, ); print __nx( '{num} book is', '{num} books are', $books, num => $books, ), __nx( ' in {num} shelf.', ' in {num} shelves.', $shelves, num => $shelves, ); =head2 Was sieht man auf den ersten Blick? Locale::Maketext hat durchnummerierte Parameter. Werden es viele, kann man sie verwechseln. Der Übersetzer, weiß nur, dass etwas eingefügt wird aber nicht was. [_1] is a [_2] in [_3]. Locale::Maketext kann mit mehreren Pluralformen in einer Textphrase umgehen. [quant,_1,book is,books are] in [*,_2,shelf,shelves]. Der Text bei Pluralformen (quant) ist nicht mehr automatisch übersetzbar, weil eine Art "oder"-Block enthalten ist. In diesem "oder"-Block ist der Platzhalter wie z.B. _1 nicht mehr enthalten. Damit sind Pluralformen nicht darstellbar, welche bereits vor der Zahl beginnen. [myplural,_1,It is _1 book,These are _1 books]. Die Funktion "myplural" gibt es natürlich nicht. *** Locale::TextDomain hat benannte Parameter, welche sich besser übersetzen lassen, weil der Übersetzer den Sinn des Satzes trotz Platzhalter immer noch verstehen kann. {name} is a {locality} in {country}. Bei mehreren Pluralformen in einer Textphrase muss diese zerlegt werden, was nicht mehr automatisch übersetzbar ist. =head2 Was man nicht gleich erkennt. =head3 Anzahl der Pluralformen Locale Maketext: Singular Singular + Plural Singular + Plural + Zero Locale::Textdomain: 2 in der Quellsprache beliebig viele in der Zielsprache Im Header jedes PO-/MO-Files steht "Plural-Forms". Das ist die Berechnungsvorschrift als C-Code mit einer Ausnahme, "OR" anstatt von "||" ist erlaubt. Diese ist sprachabhängig unterschiedlich in den einzelnen PO-/MO-Files gespeichert. Locale::Maketext ignoriert diesen Eintrag. Deutsch/Englisch: "Plural-Forms: nplurals=2; plural=n != 1\n"; Russisch: "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11" " ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" Ein Beispiel aus dem Russischen: 0 books -> книг (Plural 2) 1 book -> книга (Singular) 2 .. 4 books -> книги (Plural 1) 5 .. 20 books -> книг (Plural 2) 21 books -> книга (Singular) 22 .. 30 books -> книг (Plural 2) ... 100 books -> книг (Plural 2) 101 books -> книга (Singular) 102 .. 104 books -> книги (Plural 1) 105 .. 120 books -> книг (Plural 2) 121 book -> книга (Singular) 122 .. 124 books -> книги (Plural 1) 125 .. 130 books -> книг (Plural 2) ... 3 Pluralformen haben z.B. auch Tschechisch, Litauisch, Polnisch, Rumänisch, Slowakisch. 4 Pluralformen haben z.B. Slovenisch und Keltisch. In der EU kommen wir also mit 4 Plualformen aus. 6 Pluralformen hat Arabisch. Weil Locale::Maketext "Plural-Forms" im PO-/MO-File ignoriert, sind damit nur Sprachen mit 2 Pluralformen möglich, also Singular und Plural, so wie wir das aus Deutsch und Englisch kennen. Es gibt eine Funktion "quant", welche im Prinzip "quant2" (Singular + 1. Plural) entspricht, wenn man von der Nullform absieht. Man könnte für Locale::Maketext eine Funktion "quant3" bis "quant6" definieren. Damit müsste aber der Programmierer schon wissen, welche Textphrasen 2, 3, 4, 5 oder 6 Pluralformen benötigen. Weil er das nicht weiß, muss er dann immer "quant6" benutzen. Damit schreibt er sich die Finger wund. =head3 Position der Worte im Satz in unterschiedlichen Sprachen Die Position der einzelnen Worte kann in unterschiedlichen Sprachen unterschiedlich sein, d.h. in einer Sprache heißt es I have 2 books. und in einer anderen 2 books I have. Wenn das so ist, muss man bei Locale::Maketext komplette Sätze in den Pluralformen schreiben. Das kann der Englisch programmierende nicht wissen. Der Konflikt wird also erst während der Übersetzung bekannt. Wenn man den Konflikt umgehen möchte, schreibt man immer die kompletten Sätze. Das funktioniert aber auch nicht immer, weil Locale::Maketext nach "quant" immer "_1" erwartet und dann kommt das implizit hinzugefügte Leerzeichen und danach der Text. Gebraucht würde aber: [myplural,_1,It is _1 book.,These are _1 books.] Das ist dann aber nichts anderes als Locale::TextDoamin. =head3 Komma in den Pluralformen oder die "join and never can split"-Falle Durch simple Stringverkettung mit Komma darf kein Komma in verketteten Texten sein. Gibt es einen Quotingmechanismus wie bei Text::CSV? Mir ist keiner bekannt. I need 1 book, computer or notebook to do this. Hier ein dreckiger Workaround mit ";". I need [*_1,book; computer or notebook,books; computers or notebooks] to do this. =head3 Wert und Maßeinheit werden ggf. umgebrochen Durch Stringverkettung mit Leerzeichen entstehen Zeilenumbrüche zwischen Wert und Maßeinheit. Das ergibt je nach Zeilenlänge I have 1 book. oder I have 1 book. Für Locale::TextDomain kann man schreiben: I have {num}\N{NO-BREAK SPACE}book. I have {num}\N{NO-BREAK SPACE}books. In Locale::Maketext ist das Leerzeichen unveränderbar im Modulcode enthalten. =head2 Auszug aus dem PO-File für Locale::Maketext # header msgid "" msgstr "" "...\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "..." msgid "You can log out here." msgstr "Sie können sich hier abmelden." msgid "He lives in %1, %2." msgstr "Er wohnt in %1, %2." msgid "%quant(%1,person lives,people live) here." msgstr "%quant(%1,Mensch wohnt,Menschen wohnen) hier." # a bad workaround (no singular before placeholder) msgid "This are %quant(%1,book,books)." msgstr "Das sind %quant(%1,Buch,Bücher)." msgid "%quant(%1,book is,books are) in %quant(%2,shelf,shelves)." msgstr "%quant(%1,Buch ist,Bücher sind) in %quant(%2,Regal,Regalen)." =head2 Auszug aus dem PO-File für Locale::TextDomain # header msgid "" msgstr "" "...\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "..." msgid "You can log out here." msgstr "Sie können sich hier abmelden." msgid "He lives in {town}, {address}." msgstr "Er wohnt in {town}, {address}." msgid "{num} person lives here." msgid_plural "{num} people live here." msgstr[0] "{num} Mensch wohnt hier." msgstr[1] "{num} Menschen wohnen hier." msgid "It is {num} book." msgid_plural "These are {num} books." msgstr[0] "Es ist {num} Buch." msgstr[1] "Es sind {num} Bücher." msgid "He has {num} house in {town}, {address}." msgid_plural "He has {num} houses in {town}, {address}." msgstr[0] "Er hat {num} Haus in {town}, {address}." msgstr[1] "Er hat {num} Häuser in {town}, {address}." msgid "{num} book is" msgid_plural "{num} books are" msgstr[0] "{num} Buch ist" msgstr[1] "{num} Bücher sind" msgid " in {num} shelf." msgid_plural " in {num} shelves. msgstr[0] " in {num} Regal." msgstr[1] " in {num} Regalen." =head2 PO-File Englisch/Russisch übersetzt =head3 für Locale::Maketext # header msgid "" msgstr "" "...\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11" " ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "..." msgid "You can log out here." msgstr "Выход из системы." # Hier wäre Beugung des Stadtnamens notwendig: # Москва -> в Москве # Киев -> в Киеве # Мытищи -> в Мытищах (nicht regulär) msgid "He lives in %1, %2." msgstr "Он живет в %1, %2" # This is not correctly translatable. # The plural form for number 2 to 4 (человека живут) is not storable. msgid "%quant(%1,person lives,people live) here." msgstr "%quant(%1,человек живет,человек живут) здесь." # This is not correctly translatable. # The plural form for number 2 to 4 (дома) is not storable. msgid "He has %quant(%1,house,houses) in %2, %3." msgstr "У него %quant(%1,дом,домов) в %2, %3." # This is not correctly translatable. # The plural form for number 2 to 4 (книги) is not storable. msgid "%quant(%1,book is,books are) in %quant(%2,shelf,shelves)." msgstr "%quant(%1,книга,книг) на %quant(%1,полке,полках)." =head3 für Locale::TextDomain # header msgid "" msgstr "" "...\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11" " ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "..." msgid "You can log out here." msgstr "Выход из системы." # Hier wäre Beugung des Stadtnamens notwendig: # Москва -> в Москве # Киев -> в Киеве # Мытищи -> в Мытищах (nicht regulär) msgid "He lives in {town}, {address}." msgstr "Он живет в {town}, {address}." msgid "{num} person lives here." msgid_plural "{num} people live here." msgstr[0] "{num} человек живет здесь." msgstr[1] "{num} человека живут здесь." msgstr[2] "{num} человек живут здесь." msgid "It is {num} book." msgid_plural "These are {num} books." msgstr[0] "Это {num} книга." msgstr[1] "Это {num} книги." msgstr[2] "Это {num} книг." msgid "He has {num} house in {town}, {address}." msgid_plural "He has {num} houses in {town}, {address}." msgstr[0] "У него {num} дом в {town}, {address}." msgstr[1] "У него {num} дома в {town}, {address}." msgstr[2] "У него {num} домов в {town}, {address}." # Translate this phrase together with the next one. msgid "{num} book is" msgid_plural "{num} books are" msgstr[0] "{num} книга" msgstr[1] "{num} книги" msgstr[2] "{num} книг" # Translate this phrase together with the previous one. msgid " in {num} shelf." msgid_plural " in {num} shelves." msgstr[0] " на {num} полке." msgstr[1] " на {num} полках." msgstr[2] " на {num} полках." =head3 Beugen von "in {town}" Berlin -> Берлин in Berlin -> в Берлине Wenn man das will, muß man Platzhalterwerte auch wieder Übersetzen und dann erst einfügen. Das geht, macht aber das automatische Übersetzen der Phrase unmöglich, in dies eingefügt werden soll. Außerdem kann man diese dann auch wieder nur schwer manuell übersetzen, weil der Zusammenhang wieder etwas verloren geht. Es ist Bastelei. =head2 neutral/masculin/feminin singular/plural Beugen von Substantiven: maskulin singular -> Arzt feminin singular -> Ärztin maskulin plural -> Ärzte feminin plural -> Ärztinnen Beugen von Verben: Mascha ist zur Schule gegangen. -> Маша пошла в школу. Petja ist zur Schule gegangen. -> Петя пошёл в школу. =head2 Kontext msgid "design" msgstr "Design" msgctxt "automobile" msgid "design" msgstr "Konstruktion" msgctxt "verb" msgid "design" msgstr "zeichnen" =head2 Locale::Maketext::TPJ13 - Artikel von Sean M. Burke über Software-Lokalisierung Er schreibt: Since I wrote this article in 1998, I now see that the gettext docs are now trying more to come to terms with plurality. Whether useful conclusions have come from it is another question altogether. -- SMB, May 2001 Seitdem ich diesen Artikel 1998 schrieb, sehe ich jetzt, dass sich die gettext Dokumentationen jetzt mehr mit der Mehrzahl beschäftigen. Ob nützliche Beschlüsse davon gekommen sind, ist eine andere Frage. -- SMB, Mai 2001 Wir sind jetzt wieder viele Jahre weiter und die "Eierlegende Wollmilchsau" gibt es immer noch nicht. =head2 Software für Übersetzungsbüros Im aktuellen mir bekannten Fall, benutzt das Übersetzungsbüro die Software "SDL Trados". Es beruht wie andere vergleichbare Software auf einem "translation memory". Das funktioniert sehr gut für statische Dokumente. Für die Dynamik, welche durch Plural und Kontext in der Softwarelokalisation real existiert, scheint solche Software weniger geeignet. Sie geht von eine 1:1-Übersetzung aus. Man muss also damit rechnen, dass die anteilmäßig eher geringe Teil mit Kontext oder Pluralformen nicht gut softwareunterstützt erbracht werden kann. Im aktuellen Fall musste das POT-File in XML umgewandelt werden und dann die Zielsprache mit der Quellsprache vorbelegt werden. Diese Leistung hätte man eher vom Übersetzungsbüro erwartet. Empfehlung: Testübersetzung einer kleineren Datei durchführen lassen. Diese sollte alle typischen Konstrukte enthalten. Und das je Sprache, weil teilweise Subunternehmen eingebunden werden. =head2 Bibliographie =over 5 =item * GNU gettext I C I C =item * Singular, Plural, Dual, Trial, Quadral I C I C I C =item * CPAN-Modul Locale::Maketext I C =item * CPAN-Modul Locale::Maketext::Simple I C =item * veralteter Artikel von Sean M. Burke über Software-Lokalisierung I C =item * CPAN-Modul Locale::TextDomain I C =item * Danke für die Unterstützung, die vielen Ideen, Beispiele und Korrekturen. I C I C =back