Res.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. using System;
  2. using System.IO;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Drawing;
  6. using System.ComponentModel;
  7. using System.Diagnostics;
  8. using System.Reflection;
  9. using System.Globalization;
  10. using Microsoft.Win32;
  11. namespace FastReport.Utils
  12. {
  13. /// <summary>
  14. /// Used to get localized values from the language resource file.
  15. /// </summary>
  16. /// <remarks>
  17. /// The resource file used by default is english. To load another locale, call
  18. /// the <see cref="Res.LoadLocale(string)"/> method. It should be done at application start
  19. /// before you use any FastReport classes.
  20. /// </remarks>
  21. public static partial class Res
  22. {
  23. private static Dictionary<CultureInfo, XmlDocument> LocalesCache { get; }
  24. private static XmlDocument FLocale;
  25. private static XmlDocument FBuiltinLocale;
  26. private const string FBadResult = "NOT LOCALIZED!";
  27. /// <summary>
  28. /// Gets or set the folder that contains localization files (*.frl).
  29. /// </summary>
  30. public static string LocaleFolder
  31. {
  32. get
  33. {
  34. Report.EnsureInit();
  35. string folder = Config.Root.FindItem("Language").GetProp("Folder");
  36. // check the registry
  37. #if !CROSSPLATFORM
  38. if (String.IsNullOrEmpty(folder) && !Config.WebMode)
  39. {
  40. RegistryKey key = Registry.CurrentUser.OpenSubKey("Software").OpenSubKey("FastReports");
  41. if (key != null)
  42. {
  43. key = key.OpenSubKey("FastReport.Net");
  44. if (key != null)
  45. folder = (string)key.GetValue("LocalizationFolder", "");
  46. }
  47. }
  48. #endif
  49. // get application folder
  50. if (String.IsNullOrEmpty(folder))
  51. folder = Config.ApplicationFolder;
  52. return folder;
  53. }
  54. set {
  55. Config.Root.FindItem("Language").SetProp("Folder", value);
  56. }
  57. }
  58. /// <summary>
  59. /// Returns the current UI locale name, for example "en".
  60. /// </summary>
  61. public static string LocaleName
  62. {
  63. get
  64. {
  65. return FLocale.Root.GetProp("Name");
  66. }
  67. }
  68. internal static string DefaultLocaleName
  69. {
  70. get { return Config.Root.FindItem("Language").GetProp("Name"); }
  71. set { Config.Root.FindItem("Language").SetProp("Name", value); }
  72. }
  73. private static void LoadBuiltinLocale()
  74. {
  75. FLocale = new XmlDocument();
  76. FBuiltinLocale = FLocale;
  77. using (Stream stream = ResourceLoader.GetStream("en.xml"))
  78. {
  79. FLocale.Load(stream);
  80. CultureInfo enCulture = CultureInfo.GetCultureInfo("en");
  81. if (!LocalesCache.ContainsKey(enCulture))
  82. LocalesCache.Add(enCulture, FLocale);
  83. }
  84. }
  85. /// <summary>
  86. /// Loads the locale from a file.
  87. /// </summary>
  88. /// <param name="fileName">The name of the file that contains localized strings.</param>
  89. public static void LoadLocale(string fileName)
  90. {
  91. Report.EnsureInit();
  92. if (File.Exists(fileName))
  93. {
  94. FLocale = new XmlDocument();
  95. FLocale.Load(fileName);
  96. }
  97. else
  98. FLocale = FBuiltinLocale;
  99. }
  100. /// <summary>
  101. /// Loads and caches the locale from <see cref="CultureInfo"/> information.
  102. /// Notes: *.frl the localization file is looked for in <see cref="LocaleFolder"/>
  103. /// To work correctly, it is recommended to install FastReport.Localization package
  104. /// </summary>
  105. /// <param name="culture"></param>
  106. public static void LoadLocale(CultureInfo culture)
  107. {
  108. if (culture == CultureInfo.InvariantCulture)
  109. {
  110. FLocale = FBuiltinLocale;
  111. return;
  112. }
  113. if (LocalesCache.ContainsKey(culture))
  114. {
  115. FLocale = LocalesCache[culture];
  116. return;
  117. }
  118. // if culture not found, we try find parent culture
  119. var parent = culture.Parent;
  120. if (parent != CultureInfo.InvariantCulture)
  121. {
  122. if (LocalesCache.ContainsKey(parent))
  123. {
  124. FLocale = LocalesCache[parent];
  125. return;
  126. }
  127. // in some cultures, parent have self parent
  128. if (parent.Parent != CultureInfo.InvariantCulture)
  129. if (LocalesCache.ContainsKey(parent.Parent))
  130. {
  131. FLocale = LocalesCache[parent.Parent];
  132. return;
  133. }
  134. }
  135. string localeFolder = LocaleFolder;
  136. string localeFile = string.Empty;
  137. if (Directory.Exists(localeFolder))
  138. {
  139. localeFile = FindLocaleFile(ref culture, localeFolder);
  140. }
  141. // Find 'Localization' directory from FastReport.Localization package
  142. if (string.IsNullOrEmpty(localeFile))
  143. {
  144. localeFolder = Path.Combine(Config.ApplicationFolder, "Localization");
  145. if (Directory.Exists(localeFolder))
  146. {
  147. localeFile = FindLocaleFile(ref culture, localeFolder);
  148. }
  149. }
  150. if (!string.IsNullOrEmpty(localeFile))
  151. {
  152. Report.EnsureInit();
  153. var newLocale = new XmlDocument();
  154. newLocale.Load(localeFile);
  155. FLocale = newLocale;
  156. LocalesCache.Add(culture, newLocale);
  157. }
  158. }
  159. private static string FindLocaleFile(ref CultureInfo culture, string localeFolder)
  160. {
  161. var files = Directory.GetFiles(localeFolder, "*.frl");
  162. CultureInfo parent = culture.Parent;
  163. foreach (var file in files)
  164. {
  165. var filename = Path.GetFileNameWithoutExtension(file);
  166. if (filename == culture.EnglishName)
  167. {
  168. return file;
  169. }
  170. else
  171. {
  172. if (filename == parent.EnglishName)
  173. {
  174. culture = parent;
  175. return file;
  176. }
  177. }
  178. }
  179. return null;
  180. }
  181. /// <summary>
  182. /// Loads the locale from a stream.
  183. /// </summary>
  184. /// <param name="stream">The stream that contains localized strings.</param>
  185. public static void LoadLocale(Stream stream)
  186. {
  187. Report.EnsureInit();
  188. FLocale = new XmlDocument();
  189. FLocale.Load(stream);
  190. }
  191. /// <summary>
  192. /// Loads the english locale.
  193. /// </summary>
  194. public static void LoadEnglishLocale()
  195. {
  196. FLocale = FBuiltinLocale;
  197. }
  198. internal static void LoadDefaultLocale()
  199. {
  200. if (!Directory.Exists(LocaleFolder))
  201. return;
  202. if (String.IsNullOrEmpty(DefaultLocaleName))
  203. {
  204. // locale is set to "Auto"
  205. CultureInfo currentCulture = CultureInfo.CurrentCulture;
  206. LoadLocale(currentCulture);
  207. }
  208. else
  209. {
  210. // locale is set to specific name
  211. LoadLocale(Path.Combine(LocaleFolder, DefaultLocaleName + ".frl"));
  212. }
  213. }
  214. /// <summary>
  215. /// Gets a string with specified ID.
  216. /// </summary>
  217. /// <param name="id">The resource ID.</param>
  218. /// <returns>The localized string.</returns>
  219. /// <remarks>
  220. /// Since the locale file is xml-based, it may contain several xml node levels. For example,
  221. /// the file contains the following items:
  222. /// <code>
  223. /// &lt;Objects&gt;
  224. /// &lt;Report Text="Report"/&gt;
  225. /// &lt;Bands Text="Bands"&gt;
  226. /// &lt;ReportTitle Text="Report Title"/&gt;
  227. /// &lt;/Bands&gt;
  228. /// &lt;/Objects&gt;
  229. /// </code>
  230. /// To get the localized "ReportTitle" value, you should pass the following ID
  231. /// to this method: "Objects,Bands,ReportTitle".
  232. /// </remarks>
  233. public static string Get(string id)
  234. {
  235. string result = Get(id, FLocale);
  236. // if no item found, try built-in (english) locale
  237. if(string.IsNullOrEmpty(result))
  238. {
  239. if(FLocale != FBuiltinLocale)
  240. {
  241. result = Get(id, FBuiltinLocale);
  242. if (string.IsNullOrEmpty(result))
  243. result = id + " " + FBadResult;
  244. }
  245. else
  246. result = id + " " + FBadResult;
  247. }
  248. return result;
  249. }
  250. private static string Get(string id, XmlDocument locale)
  251. {
  252. string[] categories = id.Split(',');
  253. XmlItem xi = locale.Root;
  254. foreach (string category in categories)
  255. {
  256. int i = xi.Find(category);
  257. if (i == -1)
  258. return null;
  259. xi = xi[i];
  260. }
  261. return xi.GetProp("Text");
  262. }
  263. /// <summary>
  264. /// Get builtin string.
  265. /// </summary>
  266. /// <param name="id"></param>
  267. /// <returns></returns>
  268. public static string GetBuiltin(string id)
  269. {
  270. return Get(id, FBuiltinLocale);
  271. }
  272. /// <summary>
  273. /// Replaces the specified locale string with the new value.
  274. /// </summary>
  275. /// <param name="id">Comma-separated path to the existing locale string.</param>
  276. /// <param name="value">The new string.</param>
  277. /// <remarks>
  278. /// Use this method if you want to replace some existing locale value with the new one.
  279. /// </remarks>
  280. /// <example>
  281. /// <code>
  282. /// Res.Set("Messages,SaveChanges", "My text that will appear when you close the designer");
  283. /// </code>
  284. /// </example>
  285. public static void Set(string id, string value)
  286. {
  287. string[] categories = id.Split(',');
  288. XmlItem xi = FLocale.Root;
  289. foreach (string category in categories)
  290. {
  291. xi = xi.FindItem(category);
  292. }
  293. xi.SetProp("Text", value);
  294. }
  295. /// <summary>
  296. /// Tries to get a string with specified ID.
  297. /// </summary>
  298. /// <param name="id">The resource ID.</param>
  299. /// <returns>The localized value, if specified ID exists; otherwise, the ID itself.</returns>
  300. public static string TryGet(string id)
  301. {
  302. string result = Get(id);
  303. if (result.IndexOf(FBadResult) != -1)
  304. result = id;
  305. return result;
  306. }
  307. /// <summary>
  308. /// Tries to get builtin string with specified ID.
  309. /// </summary>
  310. /// <param name="id"></param>
  311. /// <returns></returns>
  312. public static string TryGetBuiltin(string id)
  313. {
  314. string result = GetBuiltin(id);
  315. if (string.IsNullOrEmpty(result))
  316. result = id;
  317. return result;
  318. }
  319. /// <summary>
  320. /// Checks if specified ID exists.
  321. /// </summary>
  322. /// <param name="id">The resource ID.</param>
  323. /// <returns><b>true</b> if specified ID exists.</returns>
  324. public static bool StringExists(string id)
  325. {
  326. return Get(id).IndexOf(FBadResult) == -1;
  327. }
  328. static Res()
  329. {
  330. LocalesCache = new Dictionary<CultureInfo, XmlDocument>();
  331. LoadBuiltinLocale();
  332. ResDesignExt();
  333. }
  334. static partial void ResDesignExt();
  335. }
  336. /// <summary>
  337. /// Used to access to resource IDs inside the specified branch.
  338. /// </summary>
  339. /// <remarks>
  340. /// Using the <see cref="Res.Get(string)"/> method, you have to specify the full path to your resource.
  341. /// Using this class, you can shorten the path:
  342. /// <code>
  343. /// // using the Res.Get method
  344. /// miKeepTogether = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,KeepTogether"));
  345. /// miResetPageNumber = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,ResetPageNumber"));
  346. /// miRepeatOnEveryPage = new ToolStripMenuItem(Res.Get("ComponentMenu,HeaderBand,RepeatOnEveryPage"));
  347. ///
  348. /// // using MyRes.Get method
  349. /// MyRes res = new MyRes("ComponentMenu,HeaderBand");
  350. /// miKeepTogether = new ToolStripMenuItem(res.Get("KeepTogether"));
  351. /// miResetPageNumber = new ToolStripMenuItem(res.Get("ResetPageNumber"));
  352. /// miRepeatOnEveryPage = new ToolStripMenuItem(res.Get("RepeatOnEveryPage"));
  353. ///
  354. /// </code>
  355. /// </remarks>
  356. public class MyRes
  357. {
  358. private string category;
  359. /// <summary>
  360. /// Gets a string with specified ID inside the main branch.
  361. /// </summary>
  362. /// <param name="id">The resource ID.</param>
  363. /// <returns>The localized value.</returns>
  364. public string Get(string id)
  365. {
  366. if (id != "")
  367. return Res.Get(category + "," + id);
  368. else
  369. return Res.Get(category);
  370. }
  371. /// <summary>
  372. /// Initializes a new instance of the <see cref="MyRes"/> class with spevified branch.
  373. /// </summary>
  374. /// <param name="category">The main resource branch.</param>
  375. public MyRes(string category)
  376. {
  377. this.category = category;
  378. }
  379. }
  380. /// <summary>
  381. /// Localized CategoryAttribute class.
  382. /// </summary>
  383. public class SRCategory : CategoryAttribute
  384. {
  385. /// <inheritdoc/>
  386. protected override string GetLocalizedString(string value)
  387. {
  388. return Res.TryGet("Properties,Categories," + value);
  389. }
  390. /// <summary>
  391. /// Initializes a new instance of the SRCategory class.
  392. /// </summary>
  393. /// <param name="value">The category name.</param>
  394. public SRCategory(string value)
  395. : base(value)
  396. {
  397. }
  398. }
  399. }