Jump to content
Unity Insider Forum
Mark

Mehrere Sprachen verwalten (StringTable)

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;
}

  • Like 6

Share this post


Link to post
Share on other sites

Altes Thema jedoch sehr Gut :)

 

Eines Frage habe ich jedoch hierzu ...

Wenn ich "runtime" die Sprache von "en" auf "de" umstelle, muss die StringTable ja angewiesen werden die richtige Datei neu zu laden.

Gibt es dazu auch eine Lösung?

 

LG Rokks

Share this post


Link to post
Share on other sites

Die StringTable lädt schon alle Sprachen und alle dazugehörigen Dateien welche in der Hauptdatei verfügbar sind.

Share this post


Link to post
Share on other sites

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)) {

 

Edited by Kojote

Share this post


Link to post
Share on other sites

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! :)

Edited by Kojote
Alles geklärt!

Share this post


Link to post
Share on other sites

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");

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×