/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/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.Reflection; using System.Text.RegularExpressions; using System.Xml; using Greenshot.IniFile; using log4net; using Microsoft.Win32; 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 readonly ILog Log = LogManager.GetLogger(typeof(Language)); private static readonly IList LanguagePaths = new List(); private static readonly IDictionary> LanguageFiles = new Dictionary>(); private static readonly IDictionary HelpFiles = new Dictionary(); private const string DefaultLanguage = "en-US"; private const string HelpFilenamePattern = @"help-*.html"; private const string LanguageFilenamePattern = @"language*.xml"; private static readonly Regex PrefixRegexp = new Regex(@"language_([a-zA-Z0-9]+).*"); private static readonly Regex IetfCleanRegexp = new Regex(@"[^a-zA-Z]+"); private static readonly Regex IetfRegexp = new Regex(@"^.*([a-zA-Z]{2,3}-[a-zA-Z]{2})\.xml$"); private const string LanguageGroupsKey = @"SYSTEM\CurrentControlSet\Control\Nls\Language Groups"; private static readonly IList UnsupportedLanguageGroups = new List(); private static readonly IDictionary Resources = new Dictionary(); private static string _currentLanguage; public static event LanguageChangedHandler LanguageChanged; /// /// Static initializer for the language code /// static Language() { if (!IniConfig.IsInitialized) { Log.Warn("IniConfig hasn't been initialized yet! (Design mode?)"); IniConfig.Init("greenshot", "greenshot"); } if (!LogHelper.IsInitialized) { Log.Warn("Log4net hasn't been initialized yet! (Design mode?)"); LogHelper.InitializeLog4Net(); } try { string applicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // PAF Path if (applicationFolder != null) { AddPath(Path.Combine(applicationFolder, @"App\Greenshot\Languages")); } // Application data path string applicationDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); AddPath(Path.Combine(applicationDataFolder, @"Greenshot\Languages\")); // Startup path if (applicationFolder != null) { AddPath(Path.Combine(applicationFolder, @"Languages")); } } catch (Exception pathException) { Log.Error(pathException); } try { using (RegistryKey languageGroupsKey = Registry.LocalMachine.OpenSubKey(LanguageGroupsKey, 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); } var coreConfig = IniConfig.GetIniSection(); ScanFiles(); if (!string.IsNullOrEmpty(coreConfig.Language)) { CurrentLanguage = coreConfig.Language; if (CurrentLanguage != null && CurrentLanguage != coreConfig.Language) { coreConfig.Language = CurrentLanguage; } } if (CurrentLanguage == null) { Log.Warn("Couldn't set language from configuration, changing to default. Installation problem?"); CurrentLanguage = DefaultLanguage; if (CurrentLanguage != null) { coreConfig.Language = CurrentLanguage; } } 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(DefaultLanguage); if (_currentLanguage != null && !_currentLanguage.Equals(DefaultLanguage)) { 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) { return; } try { LanguageChanged(null, null); } catch { // ignored } 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 = DefaultLanguage; } 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 = IetfCleanRegexp.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[DefaultLanguage]; } } /// /// 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 { Filepath = languageFilePath }; XmlNode node = nodes.Item(0); if (node?.Attributes != null) { 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; } 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, LanguageFilenamePattern); try { foreach (string languageFilepath in Directory.GetFiles(languagePath, LanguageFilenamePattern, 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 (IetfRegexp.IsMatch(languageFilename)) { string replacementIETF = IetfRegexp.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 (PrefixRegexp.IsMatch(languageFilename)) { languageFile.Prefix = PrefixRegexp.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 definition {0} from: {1}", languageFile.Description, languageFile.Filepath); currentFiles.Add(languageFile); } } else { currentFiles = new List {languageFile}; LanguageFiles.Add(languageFile.Ietf, currentFiles); Log.InfoFormat("Added language definition {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, HelpFilenamePattern); try { foreach (string helpFilepath in Directory.GetFiles(languagePath, HelpFilenamePattern, 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); } /// /// 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); } /// /// TryGet method which combines HasKey & GetString /// /// string with prefix /// Enum with key /// out string /// public static bool TryGetString(string prefix, Enum 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; 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); } /// /// 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 (other != null && !Prefix.Equals(other.Prefix)) { return false; } } else if (other?.Prefix != null) { return false; } if (Ietf != null) { if (other != null && !Ietf.Equals(other.Ietf)) { return false; } } else if (other?.Ietf != null) { return false; } if (Version != null) { if (other != null && !Version.Equals(other.Version)) { return false; } } else if (other != null && other.Version != null) { return false; } if (Filepath != null) { if (other != null && !Filepath.Equals(other.Filepath)) { return false; } } else if (other?.Filepath != null) { return false; } return true; } } }