/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Xml; namespace GreenshotPlugin.Core { public delegate void LanguageChangedHandler(object sender, EventArgs e); /// /// This class supplies the GUI with translations, based upon keys. /// The language resources are loaded from the language files found on fixed or supplied paths /// public class Language { private static List languagePaths = new List(); private static IDictionary> languageFiles = new Dictionary>(); private static IDictionary helpFiles = new Dictionary(); private const string DEFAULT_LANGUAGE = "en-US"; private const string HELP_FILENAME_PATTERN = @"help-*.html"; private const string LANGUAGE_FILENAME_PATTERN = @"language*.xml"; private static Regex PREFIX_REGEXP = new Regex(@"language_([a-zA-Z0-9]+).*"); private static Regex IETF_CLEAN_REGEXP = new Regex(@"[^a-zA-Z]+"); private static Regex IETF_REGEXP = new Regex(@"^.*([a-zA-Z]{2}-[a-zA-Z]{2})\.xml$"); private const string LANGUAGE_GROUPS_KEY = @"SYSTEM\CurrentControlSet\Control\Nls\Language Groups"; private static List unsupportedLanguageGroups = new List(); private static IDictionary resources = new Dictionary(); private static string currentLanguage = null; public static event LanguageChangedHandler LanguageChanged; /// /// Static initializer for the language code /// static Language() { /*try { string applicationDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string applicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // PAF Path AddPath(Path.Combine(applicationFolder, @"App\Greenshot\Languages")); // Application data path AddPath(Path.Combine(applicationDataFolder, @"Greenshot\Languages\")); // Startup path AddPath(Path.Combine(applicationFolder, @"Languages")); } catch (Exception pathException) { LOG.Error(pathException); } try { using (RegistryKey languageGroupsKey = Registry.LocalMachine.OpenSubKey(LANGUAGE_GROUPS_KEY, false)) { if (languageGroupsKey != null) { string[] groups = languageGroupsKey.GetValueNames(); foreach (string group in groups) { string groupValue = (string)languageGroupsKey.GetValue(group); bool isGroupNotInstalled = "0".Equals(groupValue); if (isGroupNotInstalled) { unsupportedLanguageGroups.Add(group.ToLower()); } } } } } catch (Exception e) { LOG.Warn("Couldn't read the installed language groups.", e); } coreConfig = IniConfig.GetIniSection(); ScanFiles(); if (!string.IsNullOrEmpty(coreConfig.Language)) { CurrentLanguage = coreConfig.Language; if (CurrentLanguage != null && CurrentLanguage != coreConfig.Language) { coreConfig.Language = CurrentLanguage; IniConfig.Save(); } } if (CurrentLanguage == null) { LOG.Warn("Couldn't set language from configuration, changing to default. Installation problem?"); CurrentLanguage = DEFAULT_LANGUAGE; if (CurrentLanguage != null) { coreConfig.Language = CurrentLanguage; IniConfig.Save(); } } if (CurrentLanguage == null) { LOG.Error("Couldn't set language, installation problem?"); }*/ } /// /// Internal method to add a path to the paths that will be scanned for language files! /// /// /// true if the path exists and is added private static bool AddPath(string path) { if (!languagePaths.Contains(path)) { if (Directory.Exists(path)) { LOG.DebugFormat("Adding language path {0}", path); languagePaths.Add(path); return true; } else { LOG.InfoFormat("Not adding non existing language path {0}", path); } } return false; } /// /// Add a new path to the paths that will be scanned for language files! /// /// /// true if the path exists and is added public static bool AddLanguageFilePath(string path) { if (!languagePaths.Contains(path)) { LOG.DebugFormat("New language path {0}", path); if (AddPath(path)) { ScanFiles(); Reload(); } else { return false; } } return true; } /// /// Load the files for the specified ietf /// /// private static void LoadFiles(string ietf) { ietf = ReformatIETF(ietf); if (!languageFiles.ContainsKey(ietf)) { LOG.ErrorFormat("No language {0} available.", ietf); return; } List filesToLoad = languageFiles[ietf]; foreach (LanguageFile fileToLoad in filesToLoad) { LoadResources(fileToLoad); } } /// /// Load the language resources from the scanned files /// private static void Reload() { resources.Clear(); LoadFiles(DEFAULT_LANGUAGE); if (currentLanguage != null && !currentLanguage.Equals(DEFAULT_LANGUAGE)) { LoadFiles(currentLanguage); } } /// /// Get or set the current language /// public static string CurrentLanguage { get { return currentLanguage; } set { string ietf = FindBestIETFMatch(value); if (!languageFiles.ContainsKey(ietf)) { LOG.WarnFormat("No match for language {0} found!", ietf); } else { if (currentLanguage == null || !currentLanguage.Equals(ietf)) { currentLanguage = ietf; Reload(); if (LanguageChanged != null) { try { LanguageChanged(null, null); } catch { } } return; } } LOG.Debug("CurrentLanguage not changed!"); } } /// /// Try to find the best match for the supplied IETF /// /// /// IETF private static string FindBestIETFMatch(string inputIETF) { string returnIETF = inputIETF; if (string.IsNullOrEmpty(returnIETF)) { returnIETF = DEFAULT_LANGUAGE; } returnIETF = ReformatIETF(returnIETF); if (!languageFiles.ContainsKey(returnIETF)) { LOG.WarnFormat("Unknown language {0}, trying best match!", returnIETF); if (returnIETF.Length == 5) { returnIETF = returnIETF.Substring(0, 2); } foreach (string availableIETF in languageFiles.Keys) { if (availableIETF.StartsWith(returnIETF)) { LOG.InfoFormat("Found language {0}, best match for {1}!", availableIETF, returnIETF); returnIETF = availableIETF; break; } } } return returnIETF; } /// /// This helper method clears all non alpha characters from the IETF, and does a reformatting. /// This prevents problems with multiple formats or typos. /// /// /// private static string ReformatIETF(string inputIETF) { string returnIETF = null; if (!string.IsNullOrEmpty(inputIETF)) { returnIETF = inputIETF.ToLower(); returnIETF = IETF_CLEAN_REGEXP.Replace(returnIETF, ""); if (returnIETF.Length == 4) { returnIETF = returnIETF.Substring(0, 2) + "-" + returnIETF.Substring(2, 2).ToUpper(); } } return returnIETF; } /// /// Return a list of all the supported languages /// public static IList SupportedLanguages { get { IList languages = new List(); // Loop over all languages with all the files in there foreach (List langs in languageFiles.Values) { // Loop over all the files for a language foreach (LanguageFile langFile in langs) { // Only take the ones without prefix, these are the "base" language files if (langFile.Prefix == null) { languages.Add(langFile); break; } } } return languages; } } /// /// Return the path to the help-file /// public static string HelpFilePath { get { if (helpFiles.ContainsKey(currentLanguage)) { return helpFiles[currentLanguage]; } return helpFiles[DEFAULT_LANGUAGE]; } } /// /// Load the resources from the language file /// /// File to load from private static void LoadResources(LanguageFile languageFile) { LOG.InfoFormat("Loading language file {0}", languageFile.Filepath); try { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(languageFile.Filepath); XmlNodeList resourceNodes = xmlDocument.GetElementsByTagName("resource"); foreach (XmlNode resourceNode in resourceNodes) { string key = resourceNode.Attributes["name"].Value; if (!string.IsNullOrEmpty(languageFile.Prefix)) { key = languageFile.Prefix + "." + key; } string text = resourceNode.InnerText; if (!string.IsNullOrEmpty(text)) { text = text.Trim(); } if (!resources.ContainsKey(key)) { resources.Add(key, text); } else { resources[key] = text; } } } catch (Exception e) { LOG.Error("Could not load language file " + languageFile.Filepath, e); } } /// /// Load the language file information /// /// /// private static LanguageFile LoadFileInfo(string languageFilePath) { try { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(languageFilePath); XmlNodeList nodes = xmlDocument.GetElementsByTagName("language"); if (nodes.Count > 0) { LanguageFile languageFile = new LanguageFile(); languageFile.Filepath = languageFilePath; XmlNode node = nodes.Item(0); languageFile.Description = node.Attributes["description"].Value; if (node.Attributes["ietf"] != null) { languageFile.Ietf = ReformatIETF(node.Attributes["ietf"].Value); } if (node.Attributes["version"] != null) { languageFile.Version = new Version(node.Attributes["version"].Value); } if (node.Attributes["prefix"] != null) { languageFile.Prefix = node.Attributes["prefix"].Value.ToLower(); } if (node.Attributes["languagegroup"] != null) { string languageGroup = node.Attributes["languagegroup"].Value; languageFile.LanguageGroup = languageGroup.ToLower(); } return languageFile; } else { throw new XmlException("Root element is missing"); } } catch (Exception e) { LOG.Error("Could not load language file " + languageFilePath, e); } return null; } /// /// Scan the files in all directories /// private static void ScanFiles() { languageFiles.Clear(); helpFiles.Clear(); foreach (string languagePath in languagePaths) { if (!Directory.Exists(languagePath)) { LOG.InfoFormat("Skipping non existing language path {0}", languagePath); continue; } LOG.InfoFormat("Searching language directory '{0}' for language files with pattern '{1}'", languagePath, LANGUAGE_FILENAME_PATTERN); try { foreach (string languageFilepath in Directory.GetFiles(languagePath, LANGUAGE_FILENAME_PATTERN, SearchOption.AllDirectories)) { //LOG.DebugFormat("Found language file: {0}", languageFilepath); LanguageFile languageFile = LoadFileInfo(languageFilepath); if (languageFile == null) { continue; } if (string.IsNullOrEmpty(languageFile.Ietf)) { LOG.WarnFormat("Fixing missing ietf in language-file {0}", languageFilepath); string languageFilename = Path.GetFileName(languageFilepath); if (IETF_REGEXP.IsMatch(languageFilename)) { string replacementIETF = IETF_REGEXP.Replace(languageFilename, "$1"); languageFile.Ietf = ReformatIETF(replacementIETF); LOG.InfoFormat("Fixed IETF to {0}", languageFile.Ietf); } else { LOG.ErrorFormat("Missing ietf , no recover possible... skipping language-file {0}!", languageFilepath); continue; } } // Check if we can display the file if (!string.IsNullOrEmpty(languageFile.LanguageGroup) && unsupportedLanguageGroups.Contains(languageFile.LanguageGroup)) { LOG.InfoFormat("Skipping unsuported (not able to display) language {0} from file {1}", languageFile.Description, languageFilepath); continue; } // build prefix, based on the filename, but only if it's not set in the file itself. if (string.IsNullOrEmpty(languageFile.Prefix)) { string languageFilename = Path.GetFileNameWithoutExtension(languageFilepath); if (PREFIX_REGEXP.IsMatch(languageFilename)) { languageFile.Prefix = PREFIX_REGEXP.Replace(languageFilename, "$1"); if (!string.IsNullOrEmpty(languageFile.Prefix)) { languageFile.Prefix = languageFile.Prefix.Replace("plugin", "").ToLower(); } } } List currentFiles = null; if (languageFiles.ContainsKey(languageFile.Ietf)) { currentFiles = languageFiles[languageFile.Ietf]; bool needToAdd = true; List deleteList = new List(); foreach (LanguageFile compareWithLangfile in currentFiles) { if ((languageFile.Prefix == null && compareWithLangfile.Prefix == null) || (languageFile.Prefix != null && languageFile.Prefix.Equals(compareWithLangfile.Prefix))) { if (compareWithLangfile.Version > languageFile.Version) { LOG.WarnFormat("Skipping {0}:{1}:{2} as {3}:{4}:{5} is newer", languageFile.Filepath, languageFile.Prefix, languageFile.Version, compareWithLangfile.Filepath, compareWithLangfile.Prefix, compareWithLangfile.Version); needToAdd = false; break; } else { LOG.WarnFormat("Found {0}:{1}:{2} and deleting {3}:{4}:{5}", languageFile.Filepath, languageFile.Prefix, languageFile.Version, compareWithLangfile.Filepath, compareWithLangfile.Prefix, compareWithLangfile.Version); deleteList.Add(compareWithLangfile); } } } if (needToAdd) { foreach (LanguageFile deleteFile in deleteList) { currentFiles.Remove(deleteFile); } LOG.InfoFormat("Added language {0} from: {1}", languageFile.Description, languageFile.Filepath); currentFiles.Add(languageFile); } } else { currentFiles = new List(); currentFiles.Add(languageFile); languageFiles.Add(languageFile.Ietf, currentFiles); LOG.InfoFormat("Added language {0} from: {1}", languageFile.Description, languageFile.Filepath); } } } catch (DirectoryNotFoundException) { LOG.InfoFormat("Non existing language directory: {0}", languagePath); } catch (Exception e) { LOG.Error("Error trying for read directory " + languagePath, e); } // Now find the help files LOG.InfoFormat("Searching language directory '{0}' for help files with pattern '{1}'", languagePath, HELP_FILENAME_PATTERN); try { foreach (string helpFilepath in Directory.GetFiles(languagePath, HELP_FILENAME_PATTERN, SearchOption.AllDirectories)) { LOG.DebugFormat("Found help file: {0}", helpFilepath); string helpFilename = Path.GetFileName(helpFilepath); string ietf = ReformatIETF(helpFilename.Replace(".html", "").Replace("help-", "")); if (!helpFiles.ContainsKey(ietf)) { helpFiles.Add(ietf, helpFilepath); } else { LOG.WarnFormat("skipping help file {0}, already a file with the same IETF {1} found!", helpFilepath, ietf); } } } catch (DirectoryNotFoundException) { LOG.InfoFormat("Non existing language directory: {0}", languagePath); } catch (Exception e) { LOG.Error("Error trying for read directory " + languagePath, e); } } } /// /// Check if a resource with prefix.key exists /// /// /// /// true if available public static bool hasKey(string prefix, Enum key) { if (key == null) { return false; } return hasKey(prefix + "." + key.ToString()); } /// /// Check if a resource with key exists /// /// /// true if available public static bool hasKey(Enum key) { if (key == null) { return false; } return hasKey(key.ToString()); } /// /// Check if a resource with prefix.key exists /// /// /// /// true if available public static bool hasKey(string prefix, string key) { return hasKey(prefix + "." + key); } /// /// Check if a resource with key exists /// /// /// true if available public static bool hasKey(string key) { if (key == null) { return false; } return resources.ContainsKey(key); } /// /// TryGet method which combines hasKey & GetString /// /// /// out string /// public static bool TryGetString(string key, out string languageString) { return resources.TryGetValue(key, out languageString); } /// /// TryGet method which combines hasKey & GetString /// /// string with prefix /// string with key /// out string /// public static bool TryGetString(string prefix, string key, out string languageString) { return resources.TryGetValue(prefix + "." + key, out languageString); } public static string Translate(object key) { string typename = key.GetType().Name; string enumKey = typename + "." + key.ToString(); if (hasKey(enumKey)) { return GetString(enumKey); } return key.ToString(); } /// /// Get the resource for key /// /// /// resource or a "string ###key### not found" public static string GetString(Enum key) { if (key == null) { return null; } return GetString(key.ToString()); } /// /// Get the resource for prefix.key /// /// /// /// resource or a "string ###prefix.key### not found" public static string GetString(string prefix, Enum key) { if (key == null) { return null; } return GetString(prefix + "." + key.ToString()); } /// /// Get the resource for prefix.key /// /// /// /// resource or a "string ###prefix.key### not found" public static string GetString(string prefix, string key) { return GetString(prefix + "." + key); } /// /// Get the resource for key /// /// /// resource or a "string ###key### not found" public static string GetString(string key) { if (key == null) { return null; } string returnValue; if (!resources.TryGetValue(key, out returnValue)) { return "string ###" + key + "### not found"; } return returnValue; } /// /// Get the resource for key, format with with string.format an supply the parameters /// /// /// formatted resource or a "string ###key### not found" public static string GetFormattedString(Enum key, object param) { return GetFormattedString(key.ToString(), param); } /// /// Get the resource for prefix.key, format with with string.format an supply the parameters /// /// /// formatted resource or a "string ###prefix.key### not found" public static string GetFormattedString(string prefix, Enum key, object param) { return GetFormattedString(prefix, key.ToString(), param); } /// /// Get the resource for prefix.key, format with with string.format an supply the parameters /// /// /// formatted resource or a "string ###prefix.key### not found" public static string GetFormattedString(string prefix, string key, object param) { return GetFormattedString(prefix + "." + key, param); } /// /// Get the resource for key, format with with string.format an supply the parameters /// /// /// formatted resource or a "string ###key### not found" public static string GetFormattedString(string key, object param) { string returnValue; if (!resources.TryGetValue(key, out returnValue)) { return "string ###" + key + "### not found"; } return String.Format(returnValue, param); } } /// /// This class contains the information about a language file /// public class LanguageFile : IEquatable { public string Description { get; set; } public string Ietf { get; set; } public Version Version { get; set; } public string LanguageGroup { get; set; } public string Filepath { get; set; } public string Prefix { get; set; } /// /// Overload equals so we can delete a entry from a collection /// /// /// public bool Equals(LanguageFile other) { if (Prefix != null) { if (!Prefix.Equals(other.Prefix)) { return false; } } else if (other.Prefix != null) { return false; } if (Ietf != null) { if (!Ietf.Equals(other.Ietf)) { return false; } } else if (other.Ietf != null) { return false; } if (Version != null) { if (!Version.Equals(other.Version)) { return false; } } else if (other.Version != null) { return false; } if (Filepath != null) { if (!Filepath.Equals(other.Filepath)) { return false; } } else if (other.Filepath != null) { return false; } return true; } } }