LocalizationManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. /// <summary>
  113. /// Gets the cultures.
  114. /// </summary>
  115. /// <returns>IEnumerable{CultureDto}.</returns>
  116. public CultureDto[] GetCultures()
  117. {
  118. var type = GetType();
  119. var path = type.Namespace + ".iso6392.txt";
  120. var list = new List<CultureDto>();
  121. using (var stream = _assemblyInfo.GetManifestResourceStream(type, path))
  122. {
  123. using (var reader = new StreamReader(stream))
  124. {
  125. while (!reader.EndOfStream)
  126. {
  127. var line = reader.ReadLine();
  128. if (!string.IsNullOrWhiteSpace(line))
  129. {
  130. var parts = line.Split('|');
  131. if (parts.Length == 5)
  132. {
  133. list.Add(new CultureDto
  134. {
  135. DisplayName = parts[3],
  136. Name = parts[3],
  137. ThreeLetterISOLanguageName = parts[0],
  138. TwoLetterISOLanguageName = parts[2]
  139. });
  140. }
  141. }
  142. }
  143. }
  144. }
  145. return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
  146. !string.IsNullOrWhiteSpace(i.DisplayName) &&
  147. !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
  148. !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
  149. }
  150. /// <summary>
  151. /// Gets the countries.
  152. /// </summary>
  153. /// <returns>IEnumerable{CountryInfo}.</returns>
  154. public CountryInfo[] GetCountries()
  155. {
  156. var type = GetType();
  157. var path = type.Namespace + ".countries.json";
  158. using (var stream = _assemblyInfo.GetManifestResourceStream(type, path))
  159. {
  160. return _jsonSerializer.DeserializeFromStream<CountryInfo[]>(stream);
  161. }
  162. }
  163. /// <summary>
  164. /// Gets the parental ratings.
  165. /// </summary>
  166. /// <returns>IEnumerable{ParentalRating}.</returns>
  167. public ParentalRating[] GetParentalRatings()
  168. {
  169. return GetParentalRatingsDictionary().Values.ToArray();
  170. }
  171. /// <summary>
  172. /// Gets the parental ratings dictionary.
  173. /// </summary>
  174. /// <returns>Dictionary{System.StringParentalRating}.</returns>
  175. private Dictionary<string, ParentalRating> GetParentalRatingsDictionary()
  176. {
  177. var countryCode = _configurationManager.Configuration.MetadataCountryCode;
  178. if (string.IsNullOrEmpty(countryCode))
  179. {
  180. countryCode = "us";
  181. }
  182. var ratings = GetRatings(countryCode);
  183. if (ratings == null)
  184. {
  185. ratings = GetRatings("us");
  186. }
  187. return ratings;
  188. }
  189. /// <summary>
  190. /// Gets the ratings.
  191. /// </summary>
  192. /// <param name="countryCode">The country code.</param>
  193. private Dictionary<string, ParentalRating> GetRatings(string countryCode)
  194. {
  195. Dictionary<string, ParentalRating> value;
  196. _allParentalRatings.TryGetValue(countryCode, out value);
  197. return value;
  198. }
  199. /// <summary>
  200. /// Loads the ratings.
  201. /// </summary>
  202. /// <param name="file">The file.</param>
  203. /// <returns>Dictionary{System.StringParentalRating}.</returns>
  204. private void LoadRatings(string file)
  205. {
  206. var dict = _fileSystem.ReadAllLines(file).Select(i =>
  207. {
  208. if (!string.IsNullOrWhiteSpace(i))
  209. {
  210. var parts = i.Split(',');
  211. if (parts.Length == 2)
  212. {
  213. int value;
  214. if (int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out value))
  215. {
  216. return new ParentalRating { Name = parts[0], Value = value };
  217. }
  218. }
  219. }
  220. return null;
  221. })
  222. .Where(i => i != null)
  223. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  224. var countryCode = _fileSystem.GetFileNameWithoutExtension(file)
  225. .Split('-')
  226. .Last();
  227. _allParentalRatings.TryAdd(countryCode, dict);
  228. }
  229. private readonly string[] _unratedValues = {"n/a", "unrated", "not rated"};
  230. /// <summary>
  231. /// Gets the rating level.
  232. /// </summary>
  233. public int? GetRatingLevel(string rating)
  234. {
  235. if (string.IsNullOrEmpty(rating))
  236. {
  237. throw new ArgumentNullException("rating");
  238. }
  239. if (_unratedValues.Contains(rating, StringComparer.OrdinalIgnoreCase))
  240. {
  241. return null;
  242. }
  243. // Fairly common for some users to have "Rated R" in their rating field
  244. rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
  245. var ratingsDictionary = GetParentalRatingsDictionary();
  246. ParentalRating value;
  247. if (!ratingsDictionary.TryGetValue(rating, out value))
  248. {
  249. // If we don't find anything check all ratings systems
  250. foreach (var dictionary in _allParentalRatings.Values)
  251. {
  252. if (dictionary.TryGetValue(rating, out value))
  253. {
  254. return value.Value;
  255. }
  256. }
  257. }
  258. return value == null ? (int?)null : value.Value;
  259. }
  260. public string GetLocalizedString(string phrase)
  261. {
  262. return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture);
  263. }
  264. public string GetLocalizedString(string phrase, string culture)
  265. {
  266. if (string.IsNullOrWhiteSpace(culture))
  267. {
  268. culture = _configurationManager.Configuration.UICulture;
  269. }
  270. if (string.IsNullOrWhiteSpace(culture))
  271. {
  272. culture = DefaultCulture;
  273. }
  274. var dictionary = GetLocalizationDictionary(culture);
  275. string value;
  276. if (dictionary.TryGetValue(phrase, out value))
  277. {
  278. return value;
  279. }
  280. return phrase;
  281. }
  282. const string DefaultCulture = "en-US";
  283. private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
  284. new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
  285. public Dictionary<string, string> GetLocalizationDictionary(string culture)
  286. {
  287. if (string.IsNullOrWhiteSpace(culture))
  288. {
  289. throw new ArgumentNullException("culture");
  290. }
  291. const string prefix = "Core";
  292. var key = prefix + culture;
  293. return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, DefaultCulture + ".json"));
  294. }
  295. private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename)
  296. {
  297. if (string.IsNullOrWhiteSpace(culture))
  298. {
  299. throw new ArgumentNullException("culture");
  300. }
  301. var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  302. var namespaceName = GetType().Namespace + "." + prefix;
  303. CopyInto(dictionary, namespaceName + "." + baseFilename);
  304. CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture));
  305. return dictionary;
  306. }
  307. private void CopyInto(IDictionary<string, string> dictionary, string resourcePath)
  308. {
  309. using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), resourcePath))
  310. {
  311. if (stream != null)
  312. {
  313. var dict = _jsonSerializer.DeserializeFromStream<Dictionary<string, string>>(stream);
  314. foreach (var key in dict.Keys)
  315. {
  316. dictionary[key] = dict[key];
  317. }
  318. }
  319. }
  320. }
  321. private string GetResourceFilename(string culture)
  322. {
  323. var parts = culture.Split('-');
  324. if (parts.Length == 2)
  325. {
  326. culture = parts[0].ToLower() + "-" + parts[1].ToUpper();
  327. }
  328. else
  329. {
  330. culture = culture.ToLower();
  331. }
  332. return culture + ".json";
  333. }
  334. public LocalizatonOption[] GetLocalizationOptions()
  335. {
  336. return new LocalizatonOption[]
  337. {
  338. new LocalizatonOption{ Name="Arabic", Value="ar"},
  339. new LocalizatonOption{ Name="Belarusian (Belarus)", Value="be-BY"},
  340. new LocalizatonOption{ Name="Bulgarian (Bulgaria)", Value="bg-BG"},
  341. new LocalizatonOption{ Name="Catalan", Value="ca"},
  342. new LocalizatonOption{ Name="Chinese Simplified", Value="zh-CN"},
  343. new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"},
  344. new LocalizatonOption{ Name="Chinese Traditional (Hong Kong)", Value="zh-HK"},
  345. new LocalizatonOption{ Name="Croatian", Value="hr"},
  346. new LocalizatonOption{ Name="Czech", Value="cs"},
  347. new LocalizatonOption{ Name="Danish", Value="da"},
  348. new LocalizatonOption{ Name="Dutch", Value="nl"},
  349. new LocalizatonOption{ Name="English (United Kingdom)", Value="en-GB"},
  350. new LocalizatonOption{ Name="English (United States)", Value="en-US"},
  351. new LocalizatonOption{ Name="Finnish", Value="fi"},
  352. new LocalizatonOption{ Name="French", Value="fr"},
  353. new LocalizatonOption{ Name="French (Canada)", Value="fr-CA"},
  354. new LocalizatonOption{ Name="German", Value="de"},
  355. new LocalizatonOption{ Name="Greek", Value="el"},
  356. new LocalizatonOption{ Name="Hebrew", Value="he"},
  357. new LocalizatonOption{ Name="Hindi (India)", Value="hi-IN"},
  358. new LocalizatonOption{ Name="Hungarian", Value="hu"},
  359. new LocalizatonOption{ Name="Indonesian", Value="id"},
  360. new LocalizatonOption{ Name="Italian", Value="it"},
  361. new LocalizatonOption{ Name="Japanese", Value="ja"},
  362. new LocalizatonOption{ Name="Kazakh", Value="kk"},
  363. new LocalizatonOption{ Name="Korean", Value="ko"},
  364. new LocalizatonOption{ Name="Lithuanian", Value="lt-LT"},
  365. new LocalizatonOption{ Name="Malay", Value="ms"},
  366. new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
  367. new LocalizatonOption{ Name="Persian", Value="fa"},
  368. new LocalizatonOption{ Name="Polish", Value="pl"},
  369. new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
  370. new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
  371. new LocalizatonOption{ Name="Romanian", Value="ro"},
  372. new LocalizatonOption{ Name="Russian", Value="ru"},
  373. new LocalizatonOption{ Name="Slovak", Value="sk"},
  374. new LocalizatonOption{ Name="Slovenian (Slovenia)", Value="sl-SI"},
  375. new LocalizatonOption{ Name="Spanish", Value="es"},
  376. new LocalizatonOption{ Name="Spanish (Latin America)", Value="es-419"},
  377. new LocalizatonOption{ Name="Spanish (Mexico)", Value="es-MX"},
  378. new LocalizatonOption{ Name="Swedish", Value="sv"},
  379. new LocalizatonOption{ Name="Swiss German", Value="gsw"},
  380. new LocalizatonOption{ Name="Turkish", Value="tr"},
  381. new LocalizatonOption{ Name="Ukrainian", Value="uk"},
  382. new LocalizatonOption{ Name="Vietnamese", Value="vi"}
  383. };
  384. }
  385. }
  386. public interface ITextLocalizer
  387. {
  388. string RemoveDiacritics(string text);
  389. string NormalizeFormKD(string text);
  390. }
  391. }