using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Reflection; using System.Xml; using System.Xml.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace CSIT { /// /// This class allows us to load a language and display the languages available. /// sealed public class LanguageHandler { #region Public Properties #region Language Updated Event Handler // Delegate Declaration. public delegate void LanguageUpdatedHandler(object sender, LanguageUpdatedArgs ca); // Event Declaration. public event LanguageUpdatedHandler LanguageChanged; #endregion // Language Updated Event Handler #region Languages /// /// This will hold a list of valid languages for this application. /// private Dictionary _languages = new Dictionary(); public Dictionary Languages { get { return this._languages; } } #endregion // Languages #region Current Lanaugage Code /// /// This will hold the code for the currently loaded language. /// private string _currentLanguageCode; public string CurrentLanguageCode { get { return this._currentLanguageCode; } set { this._currentLanguageCode = value; } } #endregion // Current Lanaugage Code #endregion // Public Properties #region Private Properties #region Language Strings /// /// This will hold a list of the loaded strings from the current language for items that cannot be done via binding. /// private Dictionary _languageStrings = new Dictionary(); #endregion // Language Strings #region Language Files /// /// This will hold a list of valid language files paths. /// private Dictionary _languageFiles = new Dictionary(); #endregion // Language Files #endregion // Private Properties /// /// Constructor. /// public LanguageHandler() { // This is represents the current assembly. Assembly currentAssembly = Assembly.GetExecutingAssembly(); // This is the version of the current assembly. Version assmeblyVersion = currentAssembly.GetName().Version; // First lets get the path of this this assembly. DirectoryInfo appDir = (new FileInfo(currentAssembly.Location)).Directory; // Next, lets see if the language directory is present. if (!Directory.Exists(appDir + @"\languages")) { throw new Exception("The language directory was not found."); } // The language directory does exist so lets set that for use later. DirectoryInfo languageDirectory = new DirectoryInfo(appDir + @"\languages"); // What we need to do now is to get a list of all the XML files in the directory. FileInfo[] langFiles = languageDirectory.GetFiles("*.xml"); if (langFiles.Length == 0) { throw new Exception("There are no language files present in the language directory."); } // This will hold the root document for each of the language files. XElement rootElement; // These will hold the language file version and language file names. string versionInfo = String.Empty, languageName = String.Empty, languageCode = String.Empty; // Now we need to cycle through each of the language files to see if they are compatible with this version of the application. foreach (FileInfo langFile in langFiles) { try { // Load the information into the rootElement = XElement.Load(langFile.FullName); // This represents the version of the application that this language file was intended for. versionInfo = rootElement.Attribute("for").Value.ToString(); // Check is this language file is valid for this version of application. if (!this.IsValidForCurrent(versionInfo, assmeblyVersion)) { continue; } // This represents the name of the language. languageName = rootElement.Attribute("name").Value.ToString(); // This represents the code for this language. languageCode = langFile.Name.Split('.')[0]; // Add this language file into the supported language list. this._languages.Add(languageCode, languageName); // And add the FileInfo object for this language file into the holder too. this._languageFiles.Add(languageCode, langFile.FullName); } catch { throw new Exception(String.Format("The language file named {0} is invalid.", langFile.Name)); } } // Check if we had more than one language available. if (this._languages.Count() == 0) { throw new Exception("There are no valid language files present in the language directory."); } } /// /// Deconstructor. Currently not used. /// ~LanguageHandler() { } #region Is Valid For Current /// /// This method checks if a given language files version string is compatible with the /// current version of the application. /// /// The version listed in the language file. /// The current version of the application assembly. /// True if the language file is compatible, false otherwise. private bool IsValidForCurrent(string fileVersion, Version assemblyVersion) { // First we need to split the file version into bits, donating the major, minor, major revision and minor revision parts. string[] versionBits = fileVersion.Split('.'); // This will tell us if the file is valid or not. bool isValid = true; // This will hold an integer version of the version bit while processing. int versionBitInt = 0; // Cycle through each of the version bits in turn. int counter = 0; foreach (string versionBit in versionBits) { // An aserix (*) means that this bit is always matching. if (versionBit == "*") { continue; } // Get the version bit as an integer. versionBitInt = Int32.Parse(versionBit); // Pass this into a switch to see if it matches. switch (counter) { case 0: isValid &= (versionBitInt >= assemblyVersion.Major); break; case 1: isValid &= (versionBitInt >= assemblyVersion.Minor); break; case 2: isValid &= (versionBitInt >= assemblyVersion.MajorRevision); break; case 3: isValid &= (versionBitInt >= assemblyVersion.MinorRevision); break; } // Was the bit valid? if (!isValid) { break; } } // And finally return the result. return isValid; } #endregion // Is Valid For Current #region Set Language Resource /// /// This method will set the localized language information for a specified window. /// /// The code for the language we want to set. public void SetLanguageResource(string languageCode) { // Get the language path from our holder. string languageFilePath = this._languageFiles[languageCode]; // Try and set the data provider resource. XmlDataProvider xmlData; try { xmlData = (XmlDataProvider)(Application.Current.FindResource("Language")); xmlData.Source = new Uri(languageFilePath, UriKind.Absolute); } catch (ResourceReferenceKeyNotFoundException ex) { throw new Exception(String.Format("The language XmlDataProvider resource could not be resolved! Message: {0}", ex.Message)); } // Update the language code is needed. if (languageCode != this.CurrentLanguageCode) { // Set the current language code to the one that we just loaded. this.CurrentLanguageCode = languageCode; // If we made it this far then we will load the XML language document for easy reference later. XDocument languageInfoFile = XDocument.Load(languageFilePath); // Clear the current string dictionary. this._languageStrings = new Dictionary(); // This will hold a list of the read strings. IEnumerable strings; try { // This will help us pick up any errors caused by incorrect XML formatting. strings = languageInfoFile.Element("CSIT").Element("Strings").Elements("String"); } catch (Exception ex) { throw new Exception(String.Format("The given language data could not be loaded. Message: {0}", ex.Message)); } // Cycle through each of the returned elements. foreach (XElement item in strings) { try { // Add this into the key holder. this._languageStrings.Add(item.Attribute("Key").Value.ToString(), item.Attribute("Text").Value.ToString()); } catch (Exception ex) { throw new Exception(String.Format("The given language data could not be loaded. Message: {0}", ex.Message)); } } } // If we made it here then fire the change event. This must be done last or code elsewhere will break. // It MUST only be fired AFTER the strings are loaded above. this.NotifyLanguageChanged(); } #endregion // Set Language Resource #region Get Localized String /// /// This method will try to load a given string from the localized string list. /// /// The name of the string to be loaded. /// A string giving the requested localized string or an exception will be thrown on an error. public string GetLocalizedString(string stringName) { // If we have already loaded this key then just return that. if (this._languageStrings.ContainsKey(stringName)) { return this._languageStrings[stringName]; } // The language information was not loaded. throw new Exception("A given language string was not present in the definition file."); } #endregion // Get Localized String #region Notify Language Changed /// /// This method will force a lanaugage to be applied to a specific window. This is usually used on the windows first load. /// public void NotifyLanguageChanged() { // If we made it here then fire the change event. This must be done last or code elsewhere will break. // It MUST only be fired AFTER the strings are loaded above. LanguageChanged(this, new LanguageUpdatedArgs()); } #endregion // Notify Language Changed } // Custom attributes. public class LanguageUpdatedArgs : System.EventArgs { /// /// This will hold the language code, if one was passed. /// private string languageCode; /// /// Constructor. /// public LanguageUpdatedArgs(string code = "") { this.languageCode = code; } /// /// Read the language code to which the value was changed. /// /// A string giving the new language code. public string LanguageCode() { return this.languageCode; } } }