changeset 6:f269d8bcc152

Re #2 - Refactor API * Refactor translations in to smaller and clearer functions * Add "get current translation language" function * Add documentation of public methods
author IBBoard <dev@ibboard.co.uk>
date Sat, 27 Dec 2008 20:28:04 +0000
parents 5ee4956dae76
children f4da31cb09d9
files Lang/Translation.cs
diffstat 1 files changed, 231 insertions(+), 137 deletions(-) [+]
line wrap: on
line diff
--- a/Lang/Translation.cs	Sat Dec 27 18:48:12 2008 +0000
+++ b/Lang/Translation.cs	Sat Dec 27 20:28:04 2008 +0000
@@ -3,7 +3,6 @@
 using System.Xml;
 using System.Xml.Schema;
 using System.Collections.Generic;
-using System.Windows.Forms;
 using System.Reflection;
 using System.ComponentModel;
 using IBBoard.IO;
@@ -13,68 +12,116 @@
 namespace IBBoard.Lang
 {
 	/// <summary>
-	/// Summary description for Translation.
+	/// A basic string translator that loads a default language and a specified language and returns translated strings that correspond to translation IDs. 
+	/// If the string doesn't exist in the specified language then the translator falls back to the default language. If the translation doesn't exist in the default language
+	/// then either a supplied value or a "no validation available" message is returned.
 	/// </summary>
 	public class Translation
-	{		
+	{		
+		private static readonly string DEFAULT_LANGUAGE = "en";
+		private static readonly string DIVIDER_STRING = "-";
 		private static string lang = "";
-		private static DirectoryInfo translationDir = null;
-		private static Dictionary<string, string> translationsLocal = null;
-		private static Dictionary<string, string> translationsDefault = null;
-
+		private static DirectoryInfo translationDir;
+		private static Dictionary<string, string> translationsLocal;
+		private static Dictionary<string, string> translationsDefault;
+
+		/// <summary>
+		/// Initialises the translations for the language specified and the default translations so that the Translation class can be used
+		/// </summary>
+		/// <param name="appPath">
+		/// The full path that the application is running from. Must contain the "translations" folder.
+		/// </param>
+		/// <param name="language">
+		/// The language to use as the load language
+		/// </param>
 		public static void InitialiseTranslations(string appPath, string language)
-		{
-			string translationPath = appPath+Constants.DirectoryString+"translations";
+		{
+			InitialiseDefaults(appPath);
+			FileInfo file = GetTranslationFile(DEFAULT_LANGUAGE);
+			XmlDocument doc = LoadTranslationDocument(file);
+			LoadTranslationsFromDocument(doc, translationsDefault);
+			translationsLocal = null;
+			LoadTranslation(lang);
+		}
+		
+		private static void InitialiseDefaults(string appPath)
+		{
+			string translationPath = appPath.TrimEnd(Constants.DirectoryChar) + Constants.DirectoryString + "translations";
 
 			if (Directory.Exists(translationPath))
-			{
+			{
+				translationsDefault = new Dictionary<string,string>();
+				translationsLocal = new Dictionary<string,string>();
 				translationDir = new DirectoryInfo(translationPath);
-
-				FileInfo file = new FileInfo(translationDir.FullName.TrimEnd(Constants.DirectoryChar)+Constants.DirectoryString+"en.translation");
-
-				if (!File.Exists(file.FullName))
-				{
-					throw new FileNotFoundException("en.translation could not be found in "+translationDir.FullName);
-				}
-                
-				translationsDefault = new Dictionary<string,string>();
-
-				XmlDocument doc = new XmlDocument();
-				XmlReaderSettings settings = new XmlReaderSettings();
-				settings.XmlResolver = new IBBXmlResolver(appPath);
-				settings.ValidationType = ValidationType.DTD;
-				settings.ProhibitDtd = false;
-				settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
-				XmlReader valReader = XmlReader.Create(file.FullName, settings);
-				doc.Load(valReader);
-				valReader.Close();
-
-				try
-				{
-					XmlNodeList translations = doc.GetElementsByTagName("translation");
-
-					if (translations.Count==0)
-					{
-						throw new InvalidFileException("No translations found in "+language+".translation");
-					}
-
-					foreach (XmlNode node in translations)
-					{
-						translationsDefault.Add(node.Attributes["id"].Value, node.InnerText);
-					}
-				}
-				catch(Exception ex)
-				{
-					throw new XmlParseException("Error while parsing "+file.FullName+": "+ex.Message);
-				}					
-				
-				translationsLocal = null;
-				LoadTranslation(lang);
 			}
 			else
 			{
 				throw new ArgumentException("Translation path must exist ("+translationPath+")");
+			}
+		}
+		
+		private static XmlDocument LoadTranslationDocument(FileInfo file)
+		{
+			XmlDocument doc = new XmlDocument();
+			XmlReaderSettings settings = new XmlReaderSettings();
+			settings.XmlResolver = new IBBXmlResolver(translationDir.Parent.FullName);
+			settings.ValidationType = ValidationType.DTD;
+			settings.ProhibitDtd = false;
+			settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
+			XmlReader valReader = XmlReader.Create(file.FullName, settings);
+			doc.Load(valReader);
+			valReader.Close();
+			return doc;
+		}
+		
+		private static FileInfo GetTranslationFile(string language)
+		{
+			FileInfo file = new FileInfo(translationDir.FullName + Constants.DirectoryString + language + ".translation");
+
+			if (!file.Exists)
+			{
+				throw new FileNotFoundException(language + "translation could not be found in "+translationDir.FullName);
+			}
+			
+			return file;
+		}
+		
+		private static void LoadTranslationsFromDocument(XmlDocument doc, Dictionary<string, string> translationTable)
+		{
+			try
+			{
+				XmlNodeList translations = doc.GetElementsByTagName("translation");
+
+				if (translations.Count==0)
+				{
+					throw new InvalidFileException("No translations found in "+GetLanguageOfDocument(doc)+".translation");
+				}
+				
+				Dictionary<string, string> tempTranslationTable = new Dictionary<string,string>();
+
+				foreach (XmlNode node in translations)
+				{
+					tempTranslationTable.Add(node.Attributes["id"].Value, node.InnerText);
+				}
+				
+				translationTable.Clear();
+				
+				foreach (string key in tempTranslationTable.Keys)
+				{
+					string translation;
+					tempTranslationTable.TryGetValue(key, out translation);
+					translationTable.Add(key, translation);
+				}
 			}
+			catch(Exception ex)
+			{
+				throw new XmlParseException("Error while parsing " + GetLanguageOfDocument(doc)+" translation: "+ex.Message);
+			}	
+		}
+		
+		private static string GetLanguageOfDocument(XmlDocument doc)
+		{
+			return doc != null ? doc.DocumentElement.GetAttribute("lang") : "";
 		}
 		
 		private static void ValidationEventMethod(object sender, ValidationEventArgs e)
@@ -82,113 +129,140 @@
 			//TODO: Fire a validation failure event
     		LogNotifier.Error(typeof(Translation), "Validation Error", e.Exception);
 		}
-
+
+		/// <summary>
+		/// Loads translations for a given language and sets them as the local language
+		/// </summary>
+		/// <param name="translationLang">
+		/// The new local language to load
+		/// </param>
 		public static void LoadTranslation(string translationLang)
 		{
-			checkInitialisation();
-
-			if (translationLang!="" && translationLang!="en")
-			{
-				FileInfo file = new FileInfo(translationDir.FullName.TrimEnd(Constants.DirectoryChar)+Constants.DirectoryString+translationLang+".translation");
-
-				if (!File.Exists(file.FullName))
-				{
-					throw new FileNotFoundException(translationLang+".translation could not be found in "+translationDir.FullName);
-				}
-
-				XmlDocument doc = new XmlDocument();
-				XmlReaderSettings settings = new XmlReaderSettings();
-				settings.XmlResolver = new IBBXmlResolver(translationDir.Parent.FullName);
-				settings.ValidationType = ValidationType.DTD;
-				settings.ProhibitDtd = false;
-				settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
-				XmlReader valReader = XmlReader.Create(file.FullName, settings);
-				doc.Load(valReader);
-				valReader.Close();
-				Dictionary<string, string> tempTranslations = new Dictionary<string, string>();
-
-				try
-				{
-					XmlNodeList translations = doc.GetElementsByTagName("translation");
+			checkInitialisation();
+			
+			if (translationLang == "" || translationLang == null)
+			{
+				throw new ArgumentException("Translation language cannot be empty or null");
+			}
 
-					if (translations.Count==0)
-					{
-						throw new InvalidFileException("No translations found in "+translationLang+".translation");
-					}
-
-					foreach (XmlNode node in translations)
-					{
-						tempTranslations.Add(node.Attributes["id"].Value, node.InnerText);
-					}
-				}
-				catch(XmlParseException)
-				{
-					throw;
-				}
-				catch(Exception ex)
-				{
-					throw new XmlParseException("Error while parsing "+file.FullName+": "+ex.Message);
-				}
-
-				translationsLocal = tempTranslations;
-				lang = translationLang;			
-			}
-			else
+			if (translationLang != DEFAULT_LANGUAGE)
 			{
-				lang = translationLang;
-			}
+				FileInfo file = GetTranslationFile(translationLang);
+				XmlDocument doc = LoadTranslationDocument(file);
+				LoadTranslationsFromDocument(doc, translationsLocal);	
+			}
+			else
+			{
+				translationsLocal.Clear();				
+			}
+			
+			lang = translationLang;
 		}
-
+
+		/// <summary>
+		/// Gets a translation for a given ID, falling back to a "missing translation" message if none can be found. Also optionally replaces any placeholders with the supplied values.
+		/// </summary>
+		/// <param name="translationID">
+		/// The ID to look up the translation for
+		/// </param>
+		/// <param name="replacements">
+		/// A collection of <see cref="System.Object"/>s to replace placeholders with
+		/// </param>
+		/// <returns>
+		/// The translation with the placeholders replaced or a "missing translation" message
+		/// </returns>
 		public static string GetTranslation(string translationID, params object[] replacements)
 		{
 			return GetTranslation(translationID, false, replacements);
 		}
-
+
+		/// <summary>
+		/// Gets a translation for a given ID, falling back to null or a warning message if a translation cannot be found. Also optionally replaces any placeholders with the supplied values.
+		/// </summary>
+		/// <param name="translationID">
+		/// The ID to look up the translation for
+		/// </param>
+		/// <param name="returnNullOnFail">
+		/// TRUE if null should be returned when no translation can be found, or FALSE if a "missing translation" message should be returned
+		/// </param>
+		/// <param name="replacements">
+		/// A collection of <see cref="System.Object"/>s to replace placeholders with
+		/// </param>
+		/// <returns>
+		/// The translation with the placeholders replaced, or a "missing translation" message or null depending on <param name="returnNullOnFail">
+		/// </returns>
 		public static string GetTranslation(string translationID, bool returnNullOnFail, params object[] replacements)
 		{
 			return GetTranslation(translationID, returnNullOnFail ? null : "", replacements);
 		}
-
+
+		/// <summary>
+		/// Gets a translation for a given ID, falling back to a supplied default if a translation cannot be found. Also optionally replaces any placeholders with the supplied values.
+		/// </summary>
+		/// <param name="translationID">
+		/// The ID to look up the translation for
+		/// </param>
+		/// <param name="defaultTranslation">
+		/// The string to return if no translation can be found. Can be null or any string.
+		/// </param>
+		/// <param name="replacements">
+		/// A collection of <see cref="System.Object"/>s to replace placeholders with
+		/// </param>
+		/// <returns>
+		/// The translation, if one exists, or the supplied default with the placeholders replaced
+		/// </returns>
 		public static string GetTranslation(string translationID, string defaultTranslation, params object[] replacements)
 		{
 			checkInitialisation();
-			string trans = null;
-
-			if (translationsLocal!=null)
-			{
-				translationsLocal.TryGetValue(translationID, out trans);
-			}
-			
-			if (trans == null)
-			{
-				translationsDefault.TryGetValue(translationID, out trans);
-			}
+			string trans = GetTranslationFromTables(translationID);
 			
 			if (trans == null)
 			{
-				//no translation so fall back to the provided default if not an empty string
-				if (defaultTranslation!="")
-				{
-					trans = defaultTranslation;
-				}
-				else
-				{
-					trans = "++ Missing Translation "+translationID+" ++";
-				}
+				trans = GetDefaultTranslation(translationID, defaultTranslation);
 			}
 
-			if (trans != null)
-			{
-				if (replacements!=null && replacements.Length > 0)
-				{
-					trans = String.Format(trans, replacements);
-				}
-				//else no need to do anything
-			}
+			trans = AddVariablesToTranslation(trans, replacements);
 
 			return trans;
+		}
+		
+		private static string GetTranslationFromTables(string translationID)
+		{
+			string translation = null;
+			
+			if (translationsLocal!=null)
+			{
+				translationsLocal.TryGetValue(translationID, out translation);
+			}
+			
+			if (translation == null)
+			{
+				translationsDefault.TryGetValue(translationID, out translation);
+			}
+			
+			return translation;
+		}
+		
+		private static string GetDefaultTranslation(string translationID, string defaultTranslation)
+		{
+			return (defaultTranslation != "" && defaultTranslation != null) ? defaultTranslation : GetMissingTranslationMessage(translationID);
 		}
-
+
+		private static string GetMissingTranslationMessage(string translationID)
+		{
+			return  "++ Missing Translation "+translationID+" ++";
+		}
+		
+		private static string AddVariablesToTranslation(string translation, object[] replacements)
+		{
+			if (translation != null && replacements != null && replacements.Length > 0)
+			{
+				translation = String.Format(translation, replacements);
+			}
+			
+			return translation;
+		}
+		
 		private static void checkInitialisation()
 		{
 			if (translationDir==null)
@@ -196,16 +270,36 @@
 				throw new InvalidOperationException("Translation class has not been initialised");
 			}
 		}
-
+
+		/// <summary>
+		/// Translate an <see cref="ITranslatable"/> item, with optional string replacement
+		/// </summary>
+		/// <param name="item">
+		/// A <see cref="ITranslatable"/> to set the text for
+		/// </param>
+		/// <param name="replacements">
+		/// A collection of <see cref="System.Object"/>s that will be used to fill place-holders
+		/// </param>
 		public static void Translate(ITranslatable item, params object[] replacements)
 		{
-			if (item.Text=="" || item.Text=="-")
+			if (item.Text == "" || item.Text == DIVIDER_STRING)
 			{
-				//if it doesn't need translating - either no text for the Dev or it's a hyphen for a divider - then bail early
+				//it doesn't need translating - either there is no text from the developer or it's a hyphen for a divider
 				return;
 			}
 			
 			item.Text = GetTranslation(item.Name, replacements);
+		}
+		
+		/// <summary>
+		/// Get the current local translation language. This is an arbitrary string used in the translation file's name and will not necessarily match the ISO language code.
+		/// </summary>
+		/// <returns>
+		/// The string used in the file name of the current local translation
+		/// </returns>
+		public static string GetTranslationLanguage()
+		{
+			return lang;
 		}
 	}
 }