LocalizationManager.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. using MediaBrowser.Model.Extensions;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Model.Entities;
  4. using MediaBrowser.Model.Globalization;
  5. using MediaBrowser.Model.Serialization;
  6. using System;
  7. using System.Collections.Concurrent;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.Linq;
  12. using MediaBrowser.Model.IO;
  13. using MediaBrowser.Model.Logging;
  14. using MediaBrowser.Model.Reflection;
  15. namespace Emby.Server.Implementations.Localization
  16. {
  17. /// <summary>
  18. /// Class LocalizationManager
  19. /// </summary>
  20. public class LocalizationManager : ILocalizationManager
  21. {
  22. /// <summary>
  23. /// The _configuration manager
  24. /// </summary>
  25. private readonly IServerConfigurationManager _configurationManager;
  26. /// <summary>
  27. /// The us culture
  28. /// </summary>
  29. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  30. private readonly ConcurrentDictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
  31. new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
  32. private readonly IFileSystem _fileSystem;
  33. private readonly IJsonSerializer _jsonSerializer;
  34. private readonly ILogger _logger;
  35. private readonly IAssemblyInfo _assemblyInfo;
  36. private readonly ITextLocalizer _textLocalizer;
  37. /// <summary>
  38. /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
  39. /// </summary>
  40. /// <param name="configurationManager">The configuration manager.</param>
  41. /// <param name="fileSystem">The file system.</param>
  42. /// <param name="jsonSerializer">The json serializer.</param>
  43. public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, IAssemblyInfo assemblyInfo, ITextLocalizer textLocalizer)
  44. {
  45. _configurationManager = configurationManager;
  46. _fileSystem = fileSystem;
  47. _jsonSerializer = jsonSerializer;
  48. _logger = logger;
  49. _assemblyInfo = assemblyInfo;
  50. _textLocalizer = textLocalizer;
  51. ExtractAll();
  52. }
  53. private void ExtractAll()
  54. {
  55. var type = GetType();
  56. var resourcePath = type.Namespace + ".Ratings.";
  57. var localizationPath = LocalizationPath;
  58. _fileSystem.CreateDirectory(localizationPath);
  59. var existingFiles = GetRatingsFiles(localizationPath)
  60. .Select(Path.GetFileName)
  61. .ToList();
  62. // Extract from the assembly
  63. foreach (var resource in _assemblyInfo
  64. .GetManifestResourceNames(type)
  65. .Where(i => i.StartsWith(resourcePath)))
  66. {
  67. var filename = "ratings-" + resource.Substring(resourcePath.Length);
  68. if (!existingFiles.Contains(filename))
  69. {
  70. using (var stream = _assemblyInfo.GetManifestResourceStream(type, resource))
  71. {
  72. var target = Path.Combine(localizationPath, filename);
  73. _logger.Info("Extracting ratings to {0}", target);
  74. using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  75. {
  76. stream.CopyTo(fs);
  77. }
  78. }
  79. }
  80. }
  81. foreach (var file in GetRatingsFiles(localizationPath))
  82. {
  83. LoadRatings(file);
  84. }
  85. }
  86. private List<string> GetRatingsFiles(string directory)
  87. {
  88. return _fileSystem.GetFilePaths(directory, false)
  89. .Where(i => string.Equals(Path.GetExtension(i), ".txt", StringComparison.OrdinalIgnoreCase))
  90. .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase))
  91. .ToList();
  92. }
  93. /// <summary>
  94. /// Gets the localization path.
  95. /// </summary>
  96. /// <value>The localization path.</value>
  97. public string LocalizationPath
  98. {
  99. get
  100. {
  101. return Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization");
  102. }
  103. }
  104. public string RemoveDiacritics(string text)
  105. {
  106. return _textLocalizer.RemoveDiacritics(text);
  107. }
  108. public string NormalizeFormKD(string text)
  109. {
  110. return _textLocalizer.NormalizeFormKD(text);
  111. }
  112. private CultureDto[] _cultures;
  113. /// <summary>
  114. /// Gets the cultures.
  115. /// </summary>
  116. /// <returns>IEnumerable{CultureDto}.</returns>
  117. public CultureDto[] GetCultures()
  118. {
  119. var result = _cultures;
  120. if (result != null)
  121. {
  122. return result;
  123. }
  124. var type = GetType();
  125. var path = type.Namespace + ".iso6392.txt";
  126. var list = new List<CultureDto>();
  127. using (var stream = _assemblyInfo.GetManifestResourceStream(type, path))
  128. {
  129. using (var reader = new StreamReader(stream))
  130. {
  131. while (!reader.EndOfStream)
  132. {
  133. var line = reader.ReadLine();
  134. if (!string.IsNullOrWhiteSpace(line))
  135. {
  136. var parts = line.Split('|');
  137. if (parts.Length == 5)
  138. {
  139. list.Add(new CultureDto
  140. {
  141. DisplayName = parts[3],
  142. Name = parts[3],
  143. ThreeLetterISOLanguageName = parts[0],
  144. TwoLetterISOLanguageName = parts[2]
  145. });
  146. }
  147. }
  148. }
  149. }
  150. }
  151. result = list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
  152. !string.IsNullOrWhiteSpace(i.DisplayName) &&
  153. !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
  154. !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
  155. _cultures = result;
  156. return result;
  157. }
  158. /// <summary>
  159. /// Gets the countries.
  160. /// </summary>
  161. /// <returns>IEnumerable{CountryInfo}.</returns>
  162. public CountryInfo[] GetCountries()
  163. {
  164. var type = GetType();
  165. var path = type.Namespace + ".countries.json";
  166. using (var stream = _assemblyInfo.GetManifestResourceStream(type, path))
  167. {
  168. return _jsonSerializer.DeserializeFromStream<CountryInfo[]>(stream);
  169. }
  170. }
  171. /// <summary>
  172. /// Gets the parental ratings.
  173. /// </summary>
  174. /// <returns>IEnumerable{ParentalRating}.</returns>
  175. public ParentalRating[] GetParentalRatings()
  176. {
  177. return GetParentalRatingsDictionary().Values.ToArray();
  178. }
  179. /// <summary>
  180. /// Gets the parental ratings dictionary.
  181. /// </summary>
  182. /// <returns>Dictionary{System.StringParentalRating}.</returns>
  183. private Dictionary<string, ParentalRating> GetParentalRatingsDictionary()
  184. {
  185. var countryCode = _configurationManager.Configuration.MetadataCountryCode;
  186. if (string.IsNullOrEmpty(countryCode))
  187. {
  188. countryCode = "us";
  189. }
  190. var ratings = GetRatings(countryCode);
  191. if (ratings == null)
  192. {
  193. ratings = GetRatings("us");
  194. }
  195. return ratings;
  196. }
  197. /// <summary>
  198. /// Gets the ratings.
  199. /// </summary>
  200. /// <param name="countryCode">The country code.</param>
  201. private Dictionary<string, ParentalRating> GetRatings(string countryCode)
  202. {
  203. Dictionary<string, ParentalRating> value;
  204. _allParentalRatings.TryGetValue(countryCode, out value);
  205. return value;
  206. }
  207. /// <summary>
  208. /// Loads the ratings.
  209. /// </summary>
  210. /// <param name="file">The file.</param>
  211. /// <returns>Dictionary{System.StringParentalRating}.</returns>
  212. private void LoadRatings(string file)
  213. {
  214. var dict = _fileSystem.ReadAllLines(file).Select(i =>
  215. {
  216. if (!string.IsNullOrWhiteSpace(i))
  217. {
  218. var parts = i.Split(',');
  219. if (parts.Length == 2)
  220. {
  221. int value;
  222. if (int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out value))
  223. {
  224. return new ParentalRating { Name = parts[0], Value = value };
  225. }
  226. }
  227. }
  228. return null;
  229. })
  230. .Where(i => i != null)
  231. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  232. var countryCode = _fileSystem.GetFileNameWithoutExtension(file)
  233. .Split('-')
  234. .Last();
  235. _allParentalRatings.TryAdd(countryCode, dict);
  236. }
  237. private readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
  238. /// <summary>
  239. /// Gets the rating level.
  240. /// </summary>
  241. public int? GetRatingLevel(string rating)
  242. {
  243. if (string.IsNullOrEmpty(rating))
  244. {
  245. throw new ArgumentNullException("rating");
  246. }
  247. if (_unratedValues.Contains(rating, StringComparer.OrdinalIgnoreCase))
  248. {
  249. return null;
  250. }
  251. // Fairly common for some users to have "Rated R" in their rating field
  252. rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
  253. var ratingsDictionary = GetParentalRatingsDictionary();
  254. ParentalRating value;
  255. if (!ratingsDictionary.TryGetValue(rating, out value))
  256. {
  257. // If we don't find anything check all ratings systems
  258. foreach (var dictionary in _allParentalRatings.Values)
  259. {
  260. if (dictionary.TryGetValue(rating, out value))
  261. {
  262. return value.Value;
  263. }
  264. }
  265. }
  266. return value == null ? (int?)null : value.Value;
  267. }
  268. public bool HasUnicodeCategory(string value, UnicodeCategory category)
  269. {
  270. foreach (var chr in value)
  271. {
  272. if (char.GetUnicodeCategory(chr) == category)
  273. {
  274. return true;
  275. }
  276. }
  277. return false;
  278. }
  279. public string GetLocalizedString(string phrase)
  280. {
  281. return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture);
  282. }
  283. public string GetLocalizedString(string phrase, string culture)
  284. {
  285. if (string.IsNullOrWhiteSpace(culture))
  286. {
  287. culture = _configurationManager.Configuration.UICulture;
  288. }
  289. if (string.IsNullOrWhiteSpace(culture))
  290. {
  291. culture = DefaultCulture;
  292. }
  293. var dictionary = GetLocalizationDictionary(culture);
  294. string value;
  295. if (dictionary.TryGetValue(phrase, out value))
  296. {
  297. return value;
  298. }
  299. return phrase;
  300. }
  301. const string DefaultCulture = "en-US";
  302. private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
  303. new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
  304. public Dictionary<string, string> GetLocalizationDictionary(string culture)
  305. {
  306. if (string.IsNullOrWhiteSpace(culture))
  307. {
  308. throw new ArgumentNullException("culture");
  309. }
  310. const string prefix = "Core";
  311. var key = prefix + culture;
  312. return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, DefaultCulture + ".json"));
  313. }
  314. private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename)
  315. {
  316. if (string.IsNullOrWhiteSpace(culture))
  317. {
  318. throw new ArgumentNullException("culture");
  319. }
  320. var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  321. var namespaceName = GetType().Namespace + "." + prefix;
  322. CopyInto(dictionary, namespaceName + "." + baseFilename);
  323. CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture));
  324. return dictionary;
  325. }
  326. private void CopyInto(IDictionary<string, string> dictionary, string resourcePath)
  327. {
  328. using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), resourcePath))
  329. {
  330. if (stream != null)
  331. {
  332. var dict = _jsonSerializer.DeserializeFromStream<Dictionary<string, string>>(stream);
  333. foreach (var key in dict.Keys)
  334. {
  335. dictionary[key] = dict[key];
  336. }
  337. }
  338. }
  339. }
  340. private string GetResourceFilename(string culture)
  341. {
  342. var parts = culture.Split('-');
  343. if (parts.Length == 2)
  344. {
  345. culture = parts[0].ToLower() + "-" + parts[1].ToUpper();
  346. }
  347. else
  348. {
  349. culture = culture.ToLower();
  350. }
  351. return culture + ".json";
  352. }
  353. public LocalizatonOption[] GetLocalizationOptions()
  354. {
  355. return new LocalizatonOption[]
  356. {
  357. new LocalizatonOption{ Name="Arabic", Value="ar"},
  358. new LocalizatonOption{ Name="Belarusian (Belarus)", Value="be-BY"},
  359. new LocalizatonOption{ Name="Bulgarian (Bulgaria)", Value="bg-BG"},
  360. new LocalizatonOption{ Name="Catalan", Value="ca"},
  361. new LocalizatonOption{ Name="Chinese Simplified", Value="zh-CN"},
  362. new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"},
  363. new LocalizatonOption{ Name="Chinese Traditional (Hong Kong)", Value="zh-HK"},
  364. new LocalizatonOption{ Name="Croatian", Value="hr"},
  365. new LocalizatonOption{ Name="Czech", Value="cs"},
  366. new LocalizatonOption{ Name="Danish", Value="da"},
  367. new LocalizatonOption{ Name="Dutch", Value="nl"},
  368. new LocalizatonOption{ Name="English (United Kingdom)", Value="en-GB"},
  369. new LocalizatonOption{ Name="English (United States)", Value="en-US"},
  370. new LocalizatonOption{ Name="Finnish", Value="fi"},
  371. new LocalizatonOption{ Name="French", Value="fr"},
  372. new LocalizatonOption{ Name="French (Canada)", Value="fr-CA"},
  373. new LocalizatonOption{ Name="German", Value="de"},
  374. new LocalizatonOption{ Name="Greek", Value="el"},
  375. new LocalizatonOption{ Name="Hebrew", Value="he"},
  376. new LocalizatonOption{ Name="Hindi (India)", Value="hi-IN"},
  377. new LocalizatonOption{ Name="Hungarian", Value="hu"},
  378. new LocalizatonOption{ Name="Indonesian", Value="id"},
  379. new LocalizatonOption{ Name="Italian", Value="it"},
  380. new LocalizatonOption{ Name="Japanese", Value="ja"},
  381. new LocalizatonOption{ Name="Kazakh", Value="kk"},
  382. new LocalizatonOption{ Name="Korean", Value="ko"},
  383. new LocalizatonOption{ Name="Lithuanian", Value="lt-LT"},
  384. new LocalizatonOption{ Name="Malay", Value="ms"},
  385. new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
  386. new LocalizatonOption{ Name="Persian", Value="fa"},
  387. new LocalizatonOption{ Name="Polish", Value="pl"},
  388. new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
  389. new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
  390. new LocalizatonOption{ Name="Romanian", Value="ro"},
  391. new LocalizatonOption{ Name="Russian", Value="ru"},
  392. new LocalizatonOption{ Name="Slovak", Value="sk"},
  393. new LocalizatonOption{ Name="Slovenian (Slovenia)", Value="sl-SI"},
  394. new LocalizatonOption{ Name="Spanish", Value="es"},
  395. new LocalizatonOption{ Name="Spanish (Latin America)", Value="es-419"},
  396. new LocalizatonOption{ Name="Spanish (Mexico)", Value="es-MX"},
  397. new LocalizatonOption{ Name="Swedish", Value="sv"},
  398. new LocalizatonOption{ Name="Swiss German", Value="gsw"},
  399. new LocalizatonOption{ Name="Turkish", Value="tr"},
  400. new LocalizatonOption{ Name="Ukrainian", Value="uk"},
  401. new LocalizatonOption{ Name="Vietnamese", Value="vi"}
  402. };
  403. }
  404. }
  405. public interface ITextLocalizer
  406. {
  407. string RemoveDiacritics(string text);
  408. string NormalizeFormKD(string text);
  409. }
  410. }