Mercurial > repos > IBBoard
view Lang/Translation.cs @ 78:da339d10c5fe
Re #32: Add staged loading of translations
* Add "XML Translation Set" that lazy loads translations from XML
* Extract some methods out to an XML extractor
* Fix header in abstract translation set
* Extract method in Translation class to add a translation set (allows combined file loading and in-code creation)
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Fri, 09 Apr 2010 19:35:18 +0000 |
parents | b1ae6fce2e3f |
children | 09f71d10c249 |
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 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 defined 'super' languages. /// If the translation doesn't exist in the hierarchy of languages then either a supplied value, null or a "no validation available" /// message is returned, depending on the parameters supplied to the method. /// /// Loaded languages are referenced by two-character language code (e.g. "en" or "it") /// </summary> public class Translation { private static readonly string DIVIDER_STRING = "-"; private static AbstractTranslationSet currentTranslations; private static Dictionary<string, AbstractTranslationSet> langToTranslationMap; public static event MethodInvoker TranslationChanged; static Translation() { langToTranslationMap = new Dictionary<string, AbstractTranslationSet>(); } /// <summary> /// Initialises the translations and loads the specified language 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 contains the "translations" folder - normally the application directory path. /// </param> /// <param name="language"> /// The language to use as the loaded language /// </param> public static void InitialiseTranslations(string appPath, string language) { InitialiseTranslations(appPath); LoadTranslationForLanguage(language); } /// <summary> /// Initialises the translation class for an application or source. /// </summary> /// <param name="appPath"> /// The full path that the contains the "translations" folder - normally the application directory path. /// </param> public static void InitialiseTranslations(string appPath) { InitialiseDefaults(appPath); } private static void InitialiseDefaults(string appPath) { Reset(); LoadTranslations(appPath); } private static void LoadTranslations(string appPath) { DirectoryInfo dir = new DirectoryInfo(Path.Combine(appPath, "translations")); if (!dir.Exists) { throw new TranslationLoadException("Translation path not found (" + dir.FullName + ")"); } TranslationXmlLoader loader = new TranslationXmlLoader(Path.Combine(appPath, "schemas/translation.xsd")); foreach (FileInfo file in dir.GetFiles("*.translation")) { AbstractTranslationSet translations = loader.LoadTranslations(file.FullName); AddTranslationSet(translations); } } /// <summary> /// Adds the supplied translation set to the list of available translations /// </summary> /// <param name="translations"> /// The translation set to add /// </param> public static void AddTranslationSet(AbstractTranslationSet translations) { langToTranslationMap[translations.LanguageCode] = translations; } /// <summary> /// Resets the loaded translations and reverts to no translations. /// </summary> public static void Reset() { SetCurrentTranslations(null); langToTranslationMap.Clear(); } private static void SetCurrentTranslations(AbstractTranslationSet newTranslations) { if (currentTranslations != newTranslations || (currentTranslations != null && !currentTranslations.Equals(newTranslations))) { currentTranslations = newTranslations; if (TranslationChanged != null) { TranslationChanged(); } } } /// <summary> /// Loads translations for a given language and sets them as the current language. /// Throws 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) { SetCurrentTranslations(GetTranslationSet(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) { string trans = GetTranslationFromTranslationSet(translationID); if (trans == null) { trans = GetDefaultTranslation(translationID, defaultTranslation); } trans = AddVariablesToTranslation(trans, replacements); return trans; } private static string GetTranslationFromTranslationSet(string translationID) { string translation = null; if (currentTranslations!=null) { translation = currentTranslations[translationID]; } 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; } /// <summary> /// Translate an <see cref="ITranslatable"/> item, with optional string replacement. If the translation /// does not exist then a warning message will be used as the translated text. /// </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) { Translate(item, GetMissingTranslationMessage(item.Name), replacements); } /// <summary> /// Translate an <see cref="ITranslatable"/> item, with optional string replacement. The <code>defaultText</code> /// can be used to specify an alternate translation. Passing <code>null</code> will result in a warning message /// about a missing translation ID. /// </summary> /// <param name="item"> /// A <see cref="ITranslatable"/> to set the text for /// </param> /// <param name="defaultText"> /// The default string to display if no translation could be found. /// </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, string defaultText, 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, defaultText, 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 (currentTranslations != null ? currentTranslations.LanguageCode : ""); } public static ICollection<TranslationLanguage> GetLanguages() { ICollection<TranslationLanguage> langs = new List<TranslationLanguage>(); foreach (AbstractTranslationSet translations in langToTranslationMap.Values) { langs.Add(translations.Language); } return langs; } public static AbstractTranslationSet GetTranslationSet(string translationLanguage) { AbstractTranslationSet translations = null; if (translationLanguage != null) { translations = DictionaryUtils.GetValue(langToTranslationMap, translationLanguage); if (translations == null) { translations = new ModifiableTranslationSet(translationLanguage); } } return translations; } } }