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