Warning: Constant ABSPATH already defined in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php on line 28 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1723 {"id":57,"date":"2008-07-11T14:16:35","date_gmt":"2008-07-11T12:16:35","guid":{"rendered":"http:\/\/www.florian-oeser.de\/?p=57"},"modified":"2009-06-18T18:46:38","modified_gmt":"2009-06-18T17:46:38","slug":"how-to-textlokalisierung-mit-net","status":"publish","type":"post","link":"http:\/\/www.florian-oeser.de\/2008\/07\/11\/how-to-textlokalisierung-mit-net\/","title":{"rendered":"How-To: Textlokalisierung mit .NET"},"content":{"rendered":"

Dieses Tutorial wurde ebenfalls auf myCSharp<\/a> und xnamag.de<\/a> ver\u00f6ffentlicht und kann auch dort diskutiert werden!<\/em><\/p>\n

Einleitung<\/h4>\n

Hallo und Herzlich Willkommen zu einem weiterem How-To. Diesmal geht es um die Textlokalisierung. Ein effizientes und zugleich einfaches System zur Lokalisierung, zumindest von Texten, muss in jeder Anwendung, die mehrsprachig ausgeliefert werden soll, vorhanden sein.<\/p>\n

Wichtig dabei ist das alle Texte in externen Dateien liegen, damit sie sich problemlos \u00fcbersetzen lassen. Texte geh\u00f6ren keinesfalls in den Programmcode. Es ist dann auch eminent wichtig das diese Textdateien eine gute Struktur haben, damit sich diese komfortabel \u00fcbersetzten lassen und in die Anwendung eingebunden werden k\u00f6nnen. Deshalb sollten alle Texte \u00fcbersichtlich formatiert und gleich in der richtigen Reihenfolge angeordnet sein. Es kann auch von Vorteil sein an diversen Stellen Zusatzinformationen an den \u00dcbersetzer mitzugeben. Wie so eine Datei aussehen kann sehen wir gleich, nur vorweg, es ist eh nicht die optimale L\u00f6sung.<\/p>\n

Ich m\u00f6chte euch bevor wir zur Umsetzung mit .NET kommen noch zwei andere M\u00f6glichkeiten zeigen wie man Texte lokalisieren kann. Danach wird dann auch deutlich warum die Umsetzung mit .NET das Beste ist.<\/p>\n

Umsetzung<\/h4>\n

Alle drei Umsetzungen m\u00f6chte ich anhand von diesem kleinen Beispiel verdeutlichen:<\/p>\n

\r\n\r\nMenuEntry optionsMenuEntry = new MenuEntry("Options"));\r\n\r\n<\/pre>\n

Generell wird alles mit C# umgesetzt!<\/p>\n

Lokalisierung \u00fcber Stringexternalisierung<\/h5>\n

Die M\u00f6glichkeit ist die einfachste aber zugleich auch die Schlechteste. Zum Einem liegt der Text direkt im Programmcode und zum Anderen muss die Sprache zur Kompilierung feststehen. F\u00fcr eine kleine Anwendung mit wenig Text ist es aber trotzdem eine sch\u00f6nere L\u00f6sung als die Texte direkt zu schreiben.<\/p>\n

Prinzipiell beruht das Verfahren auf einer statischen Klasse in der die Strings externalisiert werden.<\/p>\n

using System;
\nusing System.Collections.Generic;
\nusing System.Text;<\/p>\n

namespace MarioWorldWars.Text
\n{
\n sealed class GermanText
\n {
\n public static String OPTIONSMENU = "Optionen";
\n }
\n}<\/p>\n

MenuEntry optionsMenuEntry = new MenuEntry(GermanText.OPTIONSMENU));<\/p>\n

Lokalisierung \u00fcber externe Dateien<\/h5>\n

Kommen wir zu der M\u00f6glichkeit die ich in der Einleitung erw\u00e4hnt habe. Nun kommen alle Texte sauber in eine externe Datei. Die k\u00f6nnte zum Beispiel so ausehen:<\/p>\n

MENU_OPTS \u00a0\u00a0 \u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 „Einstellungen“
\nMENU_CONTROLLEROPTS\u00a0\u00a0\u00a0 „Steuerungseinstellungen“ ; bitte auf Plural achten<\/p><\/blockquote>\n

Hinter dem Semikolon k\u00f6nnte jetzt f\u00fcr den \u00dcbersetzter noch eine Bemerkung angebracht werden.<\/p>\n

Diese Datei wird jetzt einfach eingelesen und der entsprechende String kann zur\u00fcckgeben werden. Der Wechsel zwischen den verschiedenen Dateien ist nat\u00fcrlich problemlos zur Laufzeit m\u00f6glich. Ein kleines Beispiel zum Einlesen:<\/p>\n

using System;
\nusing System.Collections.Generic;
\nusing System.Text;
\nusing System.IO;<\/p>\n

namespace ConsoleApplication1
\n{
\n class FileReader
\n {<\/p>\n

private StreamReader sr;<\/p>\n

public FileReader()
\n {
\n \/\/read the file
\n sr = new StreamReader(@"test.txt", System.Text.Encoding.Default);<\/p>\n

}<\/p>\n

public String getString(String patter)
\n {
\n String line = string.Empty;
\n try
\n {
\n while ((line = sr.ReadLine()) != null)
\n {<\/p>\n

if (line.Contains(patter)) \/\/else read next line
\n {
\n int first = line.IndexOf(‚"‘);
\n int last = line.LastIndexOf(‚"‘);
\n \/\/return the correct text related to the pattern
\n return line = line.Substring(++first, last – first);
\n }
\n else line = "Not found";
\n }
\n }
\n catch (Exception e)
\n {
\n Console.WriteLine("The file could not be read:");
\n Console.WriteLine(e.Message);
\n }<\/p>\n

return line;
\n }
\n }
\n}<\/p>\n

Ist nat\u00fcrlich so nicht zu gebrauchen, da die Funktion fehlt, um die Datei zu wechseln.<\/p>\n

FileReader fr = new FileReader();
\nMenuEntry optionsMenuEntry = new MenuEntry(fr.getString("MENU_OPTS"));<\/p>\n

Im \u00dcbrigen wurde dieses System in der \/GameStar\/Dev<\/em> Ausgabe 01\/08 vorgestellt in der es auch generell um die Lokalisierung von Spielen ging.<\/p>\n

Lokalisierung mit .NET<\/h5>\n

Kommen wir nun zu der elegantesten L\u00f6sung. Die Textlokalisierung mit Ressourcendateien durch .NET!Das System ist sehr einfach und elegant. Auch wenn diese Dateien ja eigentlich zum Projekt geh\u00f6ren, lassen sie sich extern \u00f6ffnen und so von Lokalisierungsagenturen bearbeiten. Somit geh\u00f6ren sie nicht direkt zum Programmcode und widersprechen deshalb auch nicht der wichtigen Regel zur Lokalisierung.<\/p>\n

Hierzu legt man einfach f\u00fcr jede Sprache eine entsprechende Ressourcendatei an, in der dann die verschiedenen Texte in der jeweiligen Sprache verfasst werden. Auch da ist es direkt m\u00f6glich ein entsprechenden Kommentar hinzuzuf\u00fcgen. Die Datei k\u00f6nnte dann wie folgt aussehen:<\/p>\n

\"bsp_ressourcendatei\"<\/a><\/p>\n

Der Name<\/em> bleibt logischerweise in jeder Ressourcendatei der gleiche lediglich das Value<\/em> \u00e4ndert sich. Wichtig ist die Benennung der Datei. Das hei\u00dft das .NET Framework erkennt anhand des Suffixes um welche Sprachressource es sich handelt.<\/p>\n

Das hei\u00dft beispielsweise das die Ressouce f\u00fcr den deutschen Text den Suffix *.de-DE<\/em> am Ende des Dateinamens tragen muss. Es ist auch m\u00f6glich eine Datei ohne Suffix anzulegen. Dann wird diese ausgew\u00e4hlt wenn ein Suffix, der \u00fcbergeben wird, nicht explizit definiert wurde.<\/p>\n

Alle Ressourcendateien m\u00fcssen im \u00dcbrigen vor dem Suffix den gleichen Namen haben, damit diese von dem ResourceManager<\/em> erkannt werden. Hier in dem Beispiel ResStrings<\/em>.<\/p>\n

ResStrings.resx<\/p>\n

ResStrings.de-DE.resx<\/p>\n

ResString.cs-CZ.resx<\/p><\/blockquote>\n

Alle Suffixe gibt es in der CultureInfo-Klasse<\/a> nachzulesen.<\/p>\n

Nun schreiben wir eine Klasse die zwei statische Methoden enth\u00e4lt um global auf diese zugreifen zu k\u00f6nnen. Zum Einen eine Methode die die jeweilige Textressource ausw\u00e4hlt und zum Anderen eine die uns den entsprechenden String zur\u00fcckgibt.<\/p>\n

using System;
\nusing System.Collections.Generic;
\nusing System.Text;
\nusing System.Globalization;
\nusing System.Threading;
\nusing System.Resources;
\nusing System.Reflection;<\/p>\n

namespace Application.Text
\n{
\n sealed class Localization
\n {<\/p>\n

private static ResourceManager resMgr;<\/p>\n

public static void UpdateLanguage(string langID)
\n {
\n try
\n {
\n \/\/Set Language
\n Thread.CurrentThread.CurrentUICulture = new CultureInfo(langID);<\/p>\n

\/\/ Init ResourceManager
\n resMgr = new ResourceManager("Application.Text.ResStrings", Assembly.GetExecutingAssembly());<\/p>\n

}
\n catch (Exception ex)
\n {
\n }
\n }<\/p>\n

public static string getString(String pattern)
\n {
\n return resMgr.GetString(pattern);
\n }<\/p>\n

}
\n}<\/p>\n

Nun noch ein Beispiel wie diese Klasse im Zusammenhang mit unserem Beispiel funktioniert:<\/p>\n

Localization.UpdateLanguage("de-DE");<\/p>\n

MenuEntry optionsMenuEntry = new MenuEntry(Localization.getString("MENU_MAINOPTS"));<\/p>\n

Der Aufruf zumindest einmalige Aufruf der UpdateLangugage()<\/em> Methode ist erforderlich, weil in ihr der ResourceManager<\/em> initialisiert wird.<\/p>\n

Im \u00dcbrigen ist es auch m\u00f6glich sich die String so zur\u00fcckgeben zu lassen:<\/p>\n

MenuEntry optionsMenuEntry = new MenuEntry(ResStrings.MENU_MAINOPTS);<\/p>\n

Man ist also nicht gezwungen sich eine Methode zu schreiben, die den String zur\u00fcckgibt. Das funktioniert weil hinter der eigentlichen Ressourcendatei eine Klasse steht und man so auf deren statischen Felder zugreifen kann.<\/p>\n

Wollen wir die Sprache wechseln gen\u00fcgt der Aufruf der UpdateLangugage()<\/em> Methode:<\/p>\n

Localization.UpdateLanguage("de-DE");<\/p>\n

W\u00fcrden wir jetzt zum Beispiel den String „en-GB“ \u00fcbergeben, w\u00fcrde die Ressource ohne Suffix aufgerufen werden, da ja „en-GB“ von uns nicht explizit angeben wurde.<\/p>\n

Ich m\u00f6chte noch darauf hinweisen, das wenn ihr mit Systemen wie den WinForms arbeitet, nicht vergessen d\u00fcrft etwaige Buttons, Labels usw. dann auch zu updaten. Dies k\u00f6nnte gleich in UpdateLangugage()<\/em> erfolgen oder besser in einer separaten Methode die dann von UpdateLangugage()<\/em> aufgerufen wird.<\/p>\n

Meine Quelle f\u00fcr diese Umsetzung war das kompakte Tutorial<\/a> von Martin H.!<\/p>\n

Hilfreiche Tools<\/h4>\n

Hier m\u00f6chte ich noch kurz zwei wunderbare Tools erw\u00e4hnen, die die Arbeit mit den Ressourcendateien erheblich vereinfachen.<\/p>\n

Es kann ganz sch\u00f6n nervig sein bei mehreren Ressourcen und viel Text immer zwischen den Reitern hin- und herzuschalten. Abhilfe schafft hier der Zeta Resource Editor<\/a> von Uwe Keim. Mit diesem Tool lassen sich mehrere Ressourcen nebeneinander betrachten und bearbeitet. Sehr sehr praktisch. Die Dateien einfach speichern und im Visual Studio<\/em> die externen \u00c4nderungen best\u00e4tigen.<\/p>\n

Das zweite Tool, Resource Refactoring Tool<\/a>, ist ebenfalls sehr praktisch und erlaubt es Strings, die im Code stehen, schnell in eine Ressourcendatei zu packen. Weiterhin werden dann gleich die Strings durch den Ressourcenaufruf ersetzt.<\/p>\n

Conclusion<\/h4>\n

Das war’s dann auch schon. Ich denke ihr habt eine schicke und effiziente Methode mit der Umsetzung durch .NET gesehen. Sicherlich gibt es noch andere Wege aber dies d\u00fcrfte eine gute Basis darstellen eigene Anwendungen erfolgreich zu lokalisieren.<\/p>\n

Ich m\u00f6chte noch erw\u00e4hnen dass das .NET Framework einen sehr gro\u00dfen, hier nicht weiter er\u00f6rterten Funktionsumfang zur Lokalisierung von Anwendungen bereitstellt. Darunter z\u00e4hlen zum Beispiel Zeitangaben, Satzbau, Formatierungen, weitere Kulturinformationen und vieles mehr. Informationen dazu findet ihr unter anderem in der MSDN hier<\/a> und hier<\/a>.<\/p>\n

Bei Fragen und Anregungen k\u00f6nnt ihr mir gerne mailen oder ein Kommentar hinterlassen!<\/p>\n","protected":false},"excerpt":{"rendered":"

Dieses Tutorial wurde ebenfalls auf myCSharp und xnamag.de ver\u00f6ffentlicht und kann auch dort diskutiert werden! Einleitung Hallo und Herzlich Willkommen zu einem weiterem How-To. Diesmal geht es um die Textlokalisierung. Ein effizientes und zugleich einfaches System zur Lokalisierung, zumindest von Texten, muss in jeder Anwendung, die mehrsprachig ausgeliefert werden soll, vorhanden sein. Wichtig dabei ist […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[6],"tags":[],"_links":{"self":[{"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/posts\/57"}],"collection":[{"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/comments?post=57"}],"version-history":[{"count":2,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/posts\/57\/revisions"}],"predecessor-version":[{"id":405,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/posts\/57\/revisions\/405"}],"wp:attachment":[{"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/media?parent=57"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/categories?post=57"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.florian-oeser.de\/wp-json\/wp\/v2\/tags?post=57"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}