Jump to content
Unity Insider Forum

Mehrere Sprachen verwalten (StringTable)


Mark

Recommended Posts

Ich habe für ein Spielchen für mich eine StringTable geschrieben die ich gerne öffentlich mache:

 

Verwenden ist ganz einfach:

var yesText = StringTable.Default.GetString("Dialogs.Yes");

 

Die StringTable wird dabei automatisch die Strings laden und entsprechend der aktuellen Systemsprache die passenden Strings wählen. Man kann die aktuelle Sprache ganz einfach ändern indem man den 2 Buchstaben ISO Sprachnamen wählt:

 

StringTable.Default.CurrentLanguage = "fr"; // Französisch

 

Wenn es den gesuchten String nicht für die gewünschte Sprache gibt, so wird zu der Standardsprache zurückgegriffen (englisch), auch diese kann man ändern:

StringTable.Default.DefaultLanguage= "de"; // Deutsch

 

Man erstellt dafür XML Dateien welche die Strings enthalten, so eine Reihe von XML Dateien sieht zB so aus:

 

default.xml:

<?xml version="1.0" encoding="utf-8"?>
<StringTable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Locale Language="en" Path="strings/en-base" />
<Locale Language="de" Path="strings/de-base" />
</StringTable>

 

Alternative default.xml:

<?xml version="1.0" encoding="utf-8"?>
<StringTable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Locale Language="en">
	<String Name="Dialogs.Yes">Yes</String>
	<String Name="Dialogs.No">No</String>
</Locale>
<Locale Language="de">
	<String Name="Dialogs.Yes">Ja</String>
	<String Name="Dialogs.No">Nein</String>
</Locale>
</StringTable>

 

en-base.xml

<?xml version="1.0" encoding="utf-8"?>
<Locale Language="en" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<String Name="Dialogs.Yes">Yes</String>
<String Name="Dialogs.No">No</String>
</Locale>

 

de-base.xml


<?xml version="1.0" encoding="utf-8"?>
<Locale Language="de" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<String Name="Dialogs.Yes">Ja</String>
<String Name="Dialogs.No">Nein</String>
</Locale>

 

Die StringTable sucht im Resources Verzeichniss nach den Dateien, die Default StringTable versucht dabei von "strings/default" zu laden, erstellt dazu einfach die StringTable in einem Resources Verzeichniss mit dem Unterordner "strings".

 

Hier die 3 notwendigen C# Dateien (namespaces habe ich entfernt und durch keine sinnvollen ersetzt):

 

StringTable.cs


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Globalization;
using UnityEngine;

public class StringTable
{
private static StringTable defaultStringTable;
public static StringTable Default
{
	get
	{
		return defaultStringTable ?? (defaultStringTable = Load("strings/default"));
	}
}

public string CurrentLanguage = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
public string DefaultLanguage = "en";

[XmlElement("Locale")]
public Locale[] Locales;

private Dictionary<string, Locale> localeMap = new Dictionary<string, Locale>();

public static StringTable Load(string path)
{
	var textAsset = (TextAsset) Resources.Load(path, typeof(TextAsset));
	using (var stream = new MemoryStream(textAsset.bytes))
	{
		var serializer = new XmlSerializer(typeof(StringTable));
		var stringTable = (StringTable)serializer.Deserialize(stream);
		foreach (var locale in stringTable.Locales)
		{
			locale.Load(path);
			Locale baseLocale;
			if (!stringTable.localeMap.TryGetValue(locale.Language, out baseLocale))
				stringTable.localeMap.Add(locale.Language, locale);
			else
				baseLocale.Translations = locale.Translations.Concat(baseLocale.Translations).ToArray();
		}

		foreach (var locale in stringTable.localeMap.Values)
			locale.Initialize();

		return stringTable;
	}
}

public string GetString(string name)
{
	var text = GetString(name, CurrentLanguage);
	if (text == null)
		text = GetString(name, DefaultLanguage);
	return text ?? "Missing String '" + name + "'";
}

private string GetString(string name, string language)
{
	Locale locale;
	if (!localeMap.TryGetValue(language, out locale))
		return null;
	return locale.GetText(name);
}
}

 

Locale.cs


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using UnityEngine;

public class Locale
{
[XmlAttribute]
public string Language;

[XmlAttribute]
public string Path;

[XmlElement("String")]
public Translation[] Translations;

private Dictionary<string, string> translationMap = new Dictionary<string, string>();

internal void Initialize()
{
	foreach (var translation in Translations)
	{
		translationMap.Add(translation.Name, translation.Text);
	}
}

internal void Load(string instigatorPath)
{
	if (!string.IsNullOrEmpty(Path))
	{
		var textAsset = (TextAsset) Resources.Load(path, typeof(TextAsset));
		using (var stream = new MemoryStream(textAsset.bytes))
		{
			var serializer = new XmlSerializer(typeof(Locale));
			var locale = (Locale)serializer.Deserialize(stream);

			Translations = locale.Translations;
			Language = !string.IsNullOrEmpty(Language) ? Language : locale.Language;
		}
	}
}

public string GetText(string name)
{
	string text;
	translationMap.TryGetValue(name, out text);
	return text;
}
}

 

Translation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

public class Translation
{
[XmlAttribute]
public string Name;
[XmlText]
public string Text;
}

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 years later...
  • 2 weeks later...
  • 1 year later...

Vielen Dank, genau danach hatte ich gerade gesucht! :)

Edit:

So ganz funktionierts noch nicht, habe nun alle Scripte übernommen und wolle nun gern mal einen Text festlegen, bei mir siehts jetzt so aus:

public class StringLaden : MonoBehaviour {

    public StringTabele stringTabelle;
    public Text neuesSpiel;

	void Start () {
        neuesSpiel.text = stringTabelle.Default.GetString("Dialogs.Yes");
    }
}

Jedoch bekomme ich "stringTabelle.Default" rot unterstrichen. Er meint: "Auf den Member kann nicht mit einem Instanzverweis zugegriffen werden. Qualifizieren sie ihn stattdessen mit einem Typen."

Also so richtig versteh ich nicht, was er meint, habe doch alles nach Anleitung gemacht. :huh:

EDIT 2:

OK, Fehler gefunden, ich habe nicht über die Klasse sondern über einen Member zugegriffe.

 

Edit 3:

Problem erkannt, Gefahr gebannt, funktioniert leider immer noch nicht.

Habe nun die "StringTable.cs" und die "Translation.cs" in den Inspector gezogen. "Current Languaga" und "Default Language" sind auf "en" gestellt. In Locale ist "Language" und "Path" leer. Ich habe zudem eine "default.xml" im selben Ordner wie die Scripts, in einem Unterordner namens "strings" noch einmal eine "defautl.xml" und die anderen Sprachdateien.

Fehlermeldung gibt es eine, wenn ich das Spiel beende, Strings in den XMLs sind vorhanden, jedoch wird der neue, englische String nicht geladen.

Fehlermeldung:

NullReferenceException: Object reference not set to an instance of an object

Zeile:

using (var stream = new MemoryStream(textAsset.bytes)) {

 

bearbeitet von Kojote
Link zu diesem Kommentar
Auf anderen Seiten teilen

Habe nun noch einmal ein anderes Script versucht, selbes Problem, beim Laden der XML Datei.

xmldoc.LoadXml(textAsset.text);

Hier mal der Code:

   string language;
    XmlNode nodes;

    // Übersetzungstexte
    public Text beispielText;

    void Awake() {
        if (SystemLanguage.German == Application.systemLanguage) {
            language = "German";
        } else {
            language = "English";
        }

        TextAsset textAsset = (TextAsset)Resources.Load("lang");
        XmlDocument xmldoc = new XmlDocument();
        xmldoc.LoadXml(textAsset.text);
        for (int i = 0; i < xmldoc.ChildNodes[1].ChildNodes.Count; i++) {
            if (xmldoc.ChildNodes[1].ChildNodes[i].Name == language) {
                nodes = xmldoc.ChildNodes[1].ChildNodes[i];
            }
        }
    }

    // Use this for initialization
    void Start() {
        beispielText.text = sucheElement("Test");
    }

    public string sucheElement(string elemName) {
        for (int i = 0; i < nodes.ChildNodes.Count; i++) {
            if (nodes.ChildNodes[i].Attributes["name"].Value == elemName) {
                return nodes.ChildNodes[i].InnerText;
            }
        }
        return "";
    }

Edit:

Das ist der Knackpunkt:

TextAsset textAsset = (TextAsset)Resources.Load("lang");

Lade Datei "lang" ist richtig, "Resources" bezieht sich auf eine Klasse, diese Klasse erwartet den Ordner "Resources" im Ordner "Asset". Schiebt man da die "lang" Datei hinein, ist alles in Butter und funktioniert! :)

bearbeitet von Kojote
Alles geklärt!
Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 1 month later...

Fast vergessen das Thema, ich hab das Script etwas umgebaut, da ich mit dem Script von Mark nicht richtig zurecht kam.

Hoffe ist OK, wenn ich hier eine alternative Version als Hilfe anfüge. :)

using System.Xml;
using UnityEngine;
using UnityEngine.UI;

public class Sprachenmanager : MonoBehaviour {

    string language;
    XmlNode nodes;

    // Hier kommen alle Verbindungen zu den Texten hinein
    // Variable erstellen und Text im Inspector der Variable zuweißen
    public Text hauptmenueButtonStart;

    public void SpracheAendern(string language) {
        TextAsset textAsset = (TextAsset)Resources.Load("lang");
        XmlDocument xmldoc = new XmlDocument();
        xmldoc.LoadXml(textAsset.text);
        for (int i = 0; i < xmldoc.ChildNodes[1].ChildNodes.Count; i++) {
            if (xmldoc.ChildNodes[1].ChildNodes[i].Name == language) {
                nodes = xmldoc.ChildNodes[1].ChildNodes[i];
            }
        }
        SpracheTexteAendern();
    }

    private void SpracheTexteAendern() {
        hauptmenueButtonStart.text = sucheElement("HauptmenueButtonStart");
   }

    public string sucheElement(string elemName) {
        for (int i = 0; i < nodes.ChildNodes.Count; i++) {
            if (nodes.ChildNodes[i].Attributes["name"].Value == elemName) {
                return nodes.ChildNodes[i].InnerText;
            }
        }
        return "Text nicht gefunden!";
    }
}

Hier müsst ihr den Text, den ihr ändern wollt im Inspector schieben, z.B. für den Buttontext Start:

public Text hauptmenueButtonStart;

Damit wird der Text je nach Sprache geändert, hier für den Buttontext Start:

hauptmenueButtonStart.text = sucheElement("HauptmenueButtonStart");

Die XML, lang.xml:

<?xml version="1.0" encoding="utf-8"?>
<languages>
  <Englisch>
    <string name="HauptmenueButtonStart">START</string>
  </Englisch>

  <Deutsch>
    <string name="HauptmenueButtonStart">START</string>
  </Deutsch>

  <Franzoesisch>
    <string name="HauptmenueButtonStart">DÉBUT</string>
  </Franzoesisch>
</languages>

Sprache ändern funktioniert so:

Sprachenmanager.SpracheAendern("Englisch");
// ODER
Sprachenmanager.SpracheAendern("Deutsch");
// ODER
Sprachenmanager.SpracheAendern("Franzoesisch");

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...