using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Globalization; using Microsoft.Win32; namespace FastReport.Utils { /// /// Used to get localized values from the language resource file. /// /// /// The resource file used by default is english. To load another locale, call /// the method. It should be done at application start /// before you use any FastReport classes. /// public static partial class Res { private static Dictionary LocalesCache { get; } internal static CultureInfo CurrentCulture { get; private set; } private static XmlDocument FLocale; private static XmlDocument FBuiltinLocale; private const string FBadResult = "NOT LOCALIZED!"; /// /// Gets or set the folder that contains localization files (*.frl). /// public static string LocaleFolder { get { Report.EnsureInit(); string folder = Config.Root.FindItem("Language").GetProp("Folder"); // check the registry #if !CROSSPLATFORM if (String.IsNullOrEmpty(folder) && !Config.WebMode) { RegistryKey key = Registry.CurrentUser.OpenSubKey("Software").OpenSubKey("FastReports"); if (key != null) { key = key.OpenSubKey("FastReport.Net"); if (key != null) folder = (string)key.GetValue("LocalizationFolder", ""); } } #endif // get application folder if (String.IsNullOrEmpty(folder)) folder = Config.ApplicationFolder; return folder; } set { Config.Root.FindItem("Language").SetProp("Folder", value); } } /// /// Returns the current UI locale name, for example "en". /// public static string LocaleName { get { return FLocale.Root.GetProp("Name"); } } internal static string DefaultLocaleName { get { return Config.Root.FindItem("Language").GetProp("Name"); } set { Config.Root.FindItem("Language").SetProp("Name", value); } } private static void LoadBuiltinLocale() { FLocale = new XmlDocument(); FBuiltinLocale = FLocale; using (Stream stream = ResourceLoader.GetStream("en.xml")) { FLocale.Load(stream); CultureInfo enCulture; try { enCulture = CultureInfo.GetCultureInfo("en"); } catch // InvariantGlobalization mod fix (#939) { enCulture = CultureInfo.InvariantCulture; } CurrentCulture = enCulture; if (!LocalesCache.ContainsKey(enCulture)) LocalesCache.Add(enCulture, FLocale); } } private static void SetCurrentCulture() { try { CurrentCulture = CultureInfo.GetCultureInfo(LocaleName); } catch { } } /// /// Loads the locale from a file. /// /// The name of the file that contains localized strings. public static void LoadLocale(string fileName) { Report.EnsureInit(); if (File.Exists(fileName)) { FLocale = new XmlDocument(); FLocale.Load(fileName); SetCurrentCulture(); } else LoadEnglishLocale(); } /// /// Loads and caches the locale from information. /// Notes: *.frl the localization file is looked for in /// To work correctly, it is recommended to install FastReport.Localization package /// /// public static void LoadLocale(CultureInfo culture) { if (culture == CultureInfo.InvariantCulture) { CurrentCulture = culture; FLocale = FBuiltinLocale; return; } if (LocalesCache.ContainsKey(culture)) { CurrentCulture = culture; FLocale = LocalesCache[culture]; return; } // if culture not found, we try find parent culture var parent = culture.Parent; if (parent != CultureInfo.InvariantCulture) { if (LocalesCache.ContainsKey(parent)) { CurrentCulture = parent; FLocale = LocalesCache[parent]; return; } // in some cultures, parent have self parent if (parent.Parent != CultureInfo.InvariantCulture) if (LocalesCache.ContainsKey(parent.Parent)) { CurrentCulture = parent.Parent; FLocale = LocalesCache[parent.Parent]; return; } } string localeFolder = LocaleFolder; string localeFile = string.Empty; if (Directory.Exists(localeFolder)) { localeFile = FindLocaleFile(ref culture, localeFolder); } // Find 'Localization' directory from FastReport.Localization package if (string.IsNullOrEmpty(localeFile)) { localeFolder = Path.Combine(Config.ApplicationFolder, "Localization"); if (Directory.Exists(localeFolder)) { localeFile = FindLocaleFile(ref culture, localeFolder); } } if (!string.IsNullOrEmpty(localeFile)) { Report.EnsureInit(); var newLocale = new XmlDocument(); newLocale.Load(localeFile); CurrentCulture = culture; FLocale = newLocale; LocalesCache.Add(culture, newLocale); } } private static string FindLocaleFile(ref CultureInfo culture, string localeFolder) { var files = Directory.GetFiles(localeFolder, "*.frl"); CultureInfo parent = culture.Parent; foreach (var file in files) { var filename = Path.GetFileNameWithoutExtension(file); if (filename == culture.EnglishName) { return file; } else { if (filename == parent.EnglishName) { culture = parent; return file; } } } return null; } /// /// Loads the locale from a stream. /// /// The stream that contains localized strings. public static void LoadLocale(Stream stream) { Report.EnsureInit(); FLocale = new XmlDocument(); FLocale.Load(stream); SetCurrentCulture(); } /// /// Loads the english locale. /// public static void LoadEnglishLocale() { CurrentCulture = CultureInfo.GetCultureInfo("en"); FLocale = FBuiltinLocale; } internal static void LoadDefaultLocale() { if (!Directory.Exists(LocaleFolder)) return; if (String.IsNullOrEmpty(DefaultLocaleName)) { // locale is set to "Auto" CultureInfo currentCulture = CultureInfo.CurrentCulture; LoadLocale(currentCulture); } else { // locale is set to specific name LoadLocale(Path.Combine(LocaleFolder, DefaultLocaleName + ".frl")); } } /// /// Gets a string with specified ID. /// /// The resource ID. /// The localized string. /// /// Since the locale file is xml-based, it may contain several xml node levels. For example, /// the file contains the following items: /// /// <Objects> /// <Report Text="Report"/> /// <Bands Text="Bands"> /// <ReportTitle Text="Report Title"/> /// </Bands> /// </Objects> /// /// To get the localized "ReportTitle" value, you should pass the following ID /// to this method: "Objects,Bands,ReportTitle". /// public static string Get(string id) { string result = Get(id, FLocale); // if no item found, try built-in (english) locale if (string.IsNullOrEmpty(result)) { if (FLocale != FBuiltinLocale) { result = Get(id, FBuiltinLocale); if (string.IsNullOrEmpty(result)) result = id + " " + FBadResult; } else result = id + " " + FBadResult; } return result; } private static string Get(string id, XmlDocument locale) { string[] categories = id.Split(','); XmlItem xi = locale.Root; foreach (string category in categories) { int i = xi.Find(category); if (i == -1) return null; xi = xi[i]; } return xi.GetProp("Text"); } /// /// Get builtin string. /// /// /// public static string GetBuiltin(string id) { return Get(id, FBuiltinLocale); } /// /// Replaces the specified locale string with the new value. /// /// Comma-separated path to the existing locale string. /// The new string. /// /// Use this method if you want to replace some existing locale value with the new one. /// /// /// /// Res.Set("Messages,SaveChanges", "My text that will appear when you close the designer"); /// /// public static void Set(string id, string value) { string[] categories = id.Split(','); XmlItem xi = FLocale.Root; foreach (string category in categories) { xi = xi.FindItem(category); } xi.SetProp("Text", value); } /// /// Tries to get a string with specified ID. /// /// The resource ID. /// The localized value, if specified ID exists; otherwise, the ID itself. public static string TryGet(string id) { string result = Get(id); if (result.IndexOf(FBadResult) != -1) result = id; return result; } /// /// Tries to get builtin string with specified ID. /// /// /// public static string TryGetBuiltin(string id) { string result = GetBuiltin(id); if (string.IsNullOrEmpty(result)) result = id; return result; } /// /// Checks if specified ID exists. /// /// The resource ID. /// true if specified ID exists. public static bool StringExists(string id) { return Get(id).IndexOf(FBadResult) == -1; } static Res() { LocalesCache = new Dictionary(); LoadBuiltinLocale(); ResDesignExt(); } static partial void ResDesignExt(); } /// /// Used to access to resource IDs inside the specified branch. /// /// /// Using the method, you have to specify the full path to your resource. /// Using this class, you can shorten the path: /// /// // using the Res.Get method /// miKeepTogether = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,KeepTogether")); /// miResetPageNumber = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,ResetPageNumber")); /// miRepeatOnEveryPage = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,RepeatOnEveryPage")); /// /// // using MyRes.Get method /// MyRes res = new MyRes("ComponentMenu,HeaderBand"); /// miKeepTogether = new ToolStripMenuItem(res.Get("KeepTogether")); /// miResetPageNumber = new ToolStripMenuItem(res.Get("ResetPageNumber")); /// miRepeatOnEveryPage = new ToolStripMenuItem(res.Get("RepeatOnEveryPage")); /// /// /// public class MyRes { private string category; /// /// Gets a string with specified ID inside the main branch. /// /// The resource ID. /// The localized value. public string Get(string id) { if (id != "") return Res.Get(category + "," + id); else return Res.Get(category); } /// /// Initializes a new instance of the class with spevified branch. /// /// The main resource branch. public MyRes(string category) { this.category = category; } } /// /// Localized CategoryAttribute class. /// public class SRCategory : CategoryAttribute { /// protected override string GetLocalizedString(string value) { return Res.TryGet("Properties,Categories," + value); } /// /// Initializes a new instance of the SRCategory class. /// /// The category name. public SRCategory(string value) : base(value) { } } }