Mercurial > repos > IBBoard
view Lang/Translation.cs @ 21:c8d74202182a
Closes #14 - Throw specific exceptions from translations
* Add TranslationLoadException
* Make Translation class catch various exceptions and throw meaninful TranslationLoadExceptions
* Note potential exceptions in documentation of Translation
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Thu, 05 Mar 2009 20:34:31 +0000 |
parents | 0352fa33ee8f |
children | ea058f9ea9d4 |
line wrap: on
line source
// This file (Translation.cs) is a part of the IBBoard library and is copyright 2009 IBBoard. // // The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license. using System; using System.IO; using System.Xml; using System.Xml.Schema; using System.Collections.Generic; using System.Reflection; using System.ComponentModel; using IBBoard.IO; using IBBoard.Logging; using IBBoard.Xml; namespace IBBoard.Lang { /// <summary> /// 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; 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. /// Throws a TranslationLoadException if a problem occurred while loading translations. If this occurs then the translation methods can /// still be called but no translation will be performed. /// </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) { InitialiseDefaults(appPath); FileInfo file = GetTranslationFile(DEFAULT_LANGUAGE); XmlDocument doc = LoadTranslationDocument(file); LoadTranslationsFromDocument(doc, translationsDefault); LoadTranslationForLanguage(language); } 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); } else { throw new TranslationLoadException("Translation path not found ("+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); try { doc.Load(valReader); } catch (XmlSchemaException ex) { throw new TranslationLoadException("Problem validating schema for translation: " + ex.Message, ex); } catch (XmlException ex) { throw new TranslationLoadException("Problem reading data for translation: " + ex.Message, ex); } finally { 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 TranslationLoadException(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 TranslationLoadException("Error while parsing " + GetLanguageOfDocument(doc)+" translation: "+ex.Message, ex); } } private static string GetLanguageOfDocument(XmlDocument doc) { return doc != null ? doc.DocumentElement.GetAttribute("lang") : ""; } private static void ValidationEventMethod(object sender, ValidationEventArgs e) { //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. /// hrows a TranslationLoadException if a problem occurred while loading translations. If this occurs then the translation methods can /// still be called but all translations will fall back to the default translation. /// </summary> /// <param name="translationLang"> /// The new local language to load /// </param> public static void LoadTranslation(string translationLanguage) { if (translationLanguage == "" || translationLanguage == null) { throw new ArgumentException("Translation language cannot be null or empty"); } LoadTranslationForLanguage(translationLanguage); } private static void LoadTranslationForLanguage(string translationLanguage) { CheckInitialisation(); if (translationLanguage != DEFAULT_LANGUAGE && translationLanguage != "" && translationLanguage != null) { FileInfo file = GetTranslationFile(translationLanguage); XmlDocument doc = LoadTranslationDocument(file); LoadTranslationsFromDocument(doc, translationsLocal); } else { translationsLocal.Clear(); } lang = translationLanguage; } /// <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 = GetTranslationFromTables(translationID); if (trans == null) { trans = GetDefaultTranslation(translationID, defaultTranslation); } 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) { 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 == DIVIDER_STRING) { //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; } } }