Res.cs 15 KB

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