diff options
18 files changed, 531 insertions, 206 deletions
diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 60270a389..be86090ac 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -32,6 +32,14 @@ namespace MediaBrowser.Api } /// <summary> + /// Class ParentalRatings + /// </summary> + [Route("/Localization/Options", "GET", Summary = "Gets localization options")] + public class GetLocalizationOptions : IReturn<List<LocalizatonOption>> + { + } + + /// <summary> /// Class CulturesService /// </summary> public class LocalizationService : BaseApiService @@ -62,6 +70,13 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + public object Get(GetLocalizationOptions request) + { + var result = _localization.GetLocalizationOptions().ToList(); + + return ToOptimizedSerializedResultUsingCache(result); + } + /// <summary> /// Gets the specified request. /// </summary> diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 96b36ac7f..1a190b75e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Playback.Hls ? 0 : ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs; - var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds); + var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture)); var threads = GetNumberOfThreads(state, false); diff --git a/MediaBrowser.Controller/Localization/ILocalizationManager.cs b/MediaBrowser.Controller/Localization/ILocalizationManager.cs index dde2f7878..5993c7ac5 100644 --- a/MediaBrowser.Controller/Localization/ILocalizationManager.cs +++ b/MediaBrowser.Controller/Localization/ILocalizationManager.cs @@ -1,7 +1,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using System; using System.Collections.Generic; -using System.Threading.Tasks; namespace MediaBrowser.Controller.Localization { @@ -31,5 +31,35 @@ namespace MediaBrowser.Controller.Localization /// <param name="rating">The rating.</param> /// <returns>System.Int32.</returns> int? GetRatingLevel(string rating); + + /// <summary> + /// Gets the localized string. + /// </summary> + /// <param name="phrase">The phrase.</param> + /// <param name="culture">The culture.</param> + /// <returns>System.String.</returns> + string GetLocalizedString(string phrase, string culture); + + /// <summary> + /// Gets the localized string. + /// </summary> + /// <param name="phrase">The phrase.</param> + /// <returns>System.String.</returns> + string GetLocalizedString(string phrase); + + /// <summary> + /// Localizes the document. + /// </summary> + /// <param name="document">The document.</param> + /// <param name="culture">The culture.</param> + /// <param name="tokenBuilder">The token builder.</param> + /// <returns>System.String.</returns> + string LocalizeDocument(string document, string culture, Func<string, string> tokenBuilder); + + /// <summary> + /// Gets the localization options. + /// </summary> + /// <returns>IEnumerable{LocalizatonOption}.</returns> + IEnumerable<LocalizatonOption> GetLocalizationOptions(); } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index d0caa3ad2..932d5d63d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -218,6 +218,8 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } public string WanDdns { get; set; } + public string UICulture { get; set; } + public DlnaOptions DlnaOptions { get; set; } /// <summary> @@ -281,6 +283,8 @@ namespace MediaBrowser.Model.Configuration MetadataOptions = options.ToArray(); DlnaOptions = new DlnaOptions(); + + UICulture = "en-us"; } } diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs index 16aea8436..9f5f00d80 100644 --- a/MediaBrowser.Model/Globalization/CountryInfo.cs +++ b/MediaBrowser.Model/Globalization/CountryInfo.cs @@ -30,4 +30,10 @@ namespace MediaBrowser.Model.Globalization /// <value>The name of the three letter ISO region.</value> public string ThreeLetterISORegionName { get; set; } } + + public class LocalizatonOption + { + public string Name { get; set; } + public string Value { get; set; } + } } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json new file mode 100644 index 000000000..44600aa35 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -0,0 +1,3 @@ +{ + "LabelTest": "Text" +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs index 0d428742c..badf5f93c 100644 --- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs +++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs @@ -1,9 +1,9 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Localization; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Serialization; using MoreLinq; using System; using System.Collections.Concurrent; @@ -11,6 +11,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; +using MediaBrowser.Common.Extensions; namespace MediaBrowser.Server.Implementations.Localization { @@ -33,15 +35,17 @@ namespace MediaBrowser.Server.Implementations.Localization new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase); private readonly IFileSystem _fileSystem; - + private readonly IJsonSerializer _jsonSerializer; + /// <summary> /// Initializes a new instance of the <see cref="LocalizationManager"/> class. /// </summary> /// <param name="configurationManager">The configuration manager.</param> - public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem) + public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _configurationManager = configurationManager; _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; ExtractAll(); } @@ -241,5 +245,108 @@ namespace MediaBrowser.Server.Implementations.Localization return value == null ? (int?)null : value.Value; } + + public string GetLocalizedString(string phrase) + { + return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture); + } + + public string GetLocalizedString(string phrase, string culture) + { + var dictionary = GetLocalizationDictionary(culture); + + string value; + + if (dictionary.TryGetValue(phrase, out value)) + { + return value; + } + + return phrase; + } + + private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries = + new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); + + public Dictionary<string, string> GetLocalizationDictionary(string culture) + { + const string prefix = "Server"; + var key = prefix + culture; + + return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "server.json")); + } + + public Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture) + { + const string prefix = "JavaScript"; + var key = prefix + culture; + + return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "javascript.json")); + } + + private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename) + { + var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + var assembly = GetType().Assembly; + var namespaceName = GetType().Namespace + "." + prefix; + + CopyInto(dictionary, namespaceName + "." + baseFilename, assembly); + CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture), assembly); + + return dictionary; + } + + private void CopyInto(IDictionary<string, string> dictionary, string resourcePath, Assembly assembly) + { + using (var stream = assembly.GetManifestResourceStream(resourcePath)) + { + if (stream != null) + { + var dict = _jsonSerializer.DeserializeFromStream<Dictionary<string, string>>(stream); + + foreach (var key in dict.Keys) + { + dictionary[key] = dict[key]; + } + } + } + } + + private string GetResourceFilename(string culture) + { + var parts = culture.Split('-'); + + if (parts.Length == 2) + { + culture = parts[0].ToLower() + "_" + parts[1].ToUpper(); + } + else + { + culture = culture.ToLower(); + } + + return culture + ".json"; + } + + public IEnumerable<LocalizatonOption> GetLocalizationOptions() + { + return new List<LocalizatonOption> + { + new LocalizatonOption{ Name="English (United States)", Value="en-us"} + }; + } + + public string LocalizeDocument(string document, string culture, Func<string,string> tokenBuilder) + { + foreach (var pair in GetLocalizationDictionary(culture).ToList()) + { + var token = tokenBuilder(pair.Key); + + document = document.Replace(token, pair.Value, StringComparison.Ordinal); + } + + return document; + } } } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json new file mode 100644 index 000000000..62a418391 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -0,0 +1,17 @@ +{ + "LabelExit": "Exit", + "LabelVisitCommunity": "Visit Community", + "LabelGithubWiki": "Github Wiki", + "LabelSwagger": "Swagger", + "LabelStandard": "Standard", + "LabelViewApiDocumentation": "View Api Documentation", + "LabelBrowseLibrary": "Browse Library", + "LabelConfigureMediaBrowser": "Configure Media Browser", + "LabelOpenLibraryViewer": "Open Library Viewer", + "LabelRestartServer": "Restart Server", + "LabelShowLogWindow": "Show Log Window", + "LabelPrevious": "Previous", + "LabelFinish": "Finish", + "LabelNext": "Next", + "LabelYoureDone": "You're Done!" +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 115bc0136..ac8dc9431 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -282,6 +282,8 @@ <EmbeddedResource Include="Localization\Ratings\ru.txt" /> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="Localization\JavaScript\javascript.json" /> + <EmbeddedResource Include="Localization\Server\server.json" /> <None Include="packages.config" /> </ItemGroup> <ItemGroup> @@ -386,6 +388,7 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> + <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 4c75b6502..2575434f4 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.ServerApplication private ILiveTvManager LiveTvManager { get; set; } - private ILocalizationManager LocalizationManager { get; set; } + internal ILocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } private IChannelManager ChannelManager { get; set; } @@ -421,6 +421,9 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(ServerConfigurationManager); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer); + RegisterSingleInstance(LocalizationManager); + RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger)); RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer()); @@ -472,9 +475,6 @@ namespace MediaBrowser.ServerApplication ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager); RegisterSingleInstance(ServerManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager); - RegisterSingleInstance(LocalizationManager); - var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15)); diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs index 6c40b549b..8e9091906 100644 --- a/MediaBrowser.ServerApplication/LibraryViewer.cs +++ b/MediaBrowser.ServerApplication/LibraryViewer.cs @@ -16,18 +16,16 @@ namespace MediaBrowser.ServerApplication { private readonly IJsonSerializer _jsonSerializer; private readonly ILibraryManager _libraryManager; - private readonly IDisplayPreferencesRepository _displayPreferencesManager; private readonly IItemRepository _itemRepository; private User _currentUser; - public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo) + public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo) { InitializeComponent(); _jsonSerializer = jsonSerializer; _libraryManager = libraryManager; - _displayPreferencesManager = displayPreferencesManager; _itemRepository = itemRepo; foreach (var user in userManager.Users) @@ -44,7 +42,7 @@ namespace MediaBrowser.ServerApplication if (e.Node != null) { var item = (BaseItem)e.Node.Tag; - lblType.Text = "Type: " + item.GetType().Name; + lblType.Text = item.GetType().Name; var json = FormatJson(_jsonSerializer.SerializeToString(item)); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 3d490a1f8..d8bd3938e 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.ServerApplication { //Application.EnableVisualStyles(); //Application.SetCompatibleTextRenderingDefault(false); - _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository); + _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository, _appHost.LocalizationManager); Application.Run(); } diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index 99b6db2d7..27f47787f 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -4,6 +4,7 @@ using System.Windows.Forms; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -41,6 +42,7 @@ namespace MediaBrowser.ServerApplication private readonly IJsonSerializer _jsonSerializer; private readonly IDisplayPreferencesRepository _displayPreferencesManager; private readonly IItemRepository _itemRepository; + private readonly ILocalizationManager _localization; private LogForm _logForm; public bool Visible @@ -56,10 +58,17 @@ namespace MediaBrowser.ServerApplication } } - public ServerNotifyIcon(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo) + public ServerNotifyIcon(ILogManager logManager, + IServerApplicationHost appHost, + IServerConfigurationManager configurationManager, + IUserManager userManager, ILibraryManager libraryManager, + IJsonSerializer jsonSerializer, + IDisplayPreferencesRepository displayPreferencesManager, + IItemRepository itemRepo, ILocalizationManager localization) { _logger = logManager.GetLogger("MainWindow"); _itemRepository = itemRepo; + _localization = localization; _appHost = appHost; _logManager = logManager; _configurationManager = configurationManager; @@ -118,20 +127,17 @@ namespace MediaBrowser.ServerApplication // cmdExit.Name = "cmdExit"; cmdExit.Size = new System.Drawing.Size(208, 22); - cmdExit.Text = "Exit"; // // cmdCommunity // cmdCommunity.Name = "cmdCommunity"; cmdCommunity.Size = new System.Drawing.Size(208, 22); - cmdCommunity.Text = "Visit Community"; // // cmdLogWindow // cmdLogWindow.CheckOnClick = true; cmdLogWindow.Name = "cmdLogWindow"; cmdLogWindow.Size = new System.Drawing.Size(208, 22); - cmdLogWindow.Text = "Show Log Window"; // // toolStripSeparator1 // @@ -142,13 +148,11 @@ namespace MediaBrowser.ServerApplication // cmdRestart.Name = "cmdRestart"; cmdRestart.Size = new System.Drawing.Size(208, 22); - cmdRestart.Text = "Restart Server"; // // cmdLibraryExplorer // cmdLibraryExplorer.Name = "cmdLibraryExplorer"; cmdLibraryExplorer.Size = new System.Drawing.Size(208, 22); - cmdLibraryExplorer.Text = "Open Library Explorer"; // // toolStripSeparator2 // @@ -159,13 +163,11 @@ namespace MediaBrowser.ServerApplication // cmdConfigure.Name = "cmdConfigure"; cmdConfigure.Size = new System.Drawing.Size(208, 22); - cmdConfigure.Text = "Configure Media Browser"; // // cmdBrowse // cmdBrowse.Name = "cmdBrowse"; cmdBrowse.Size = new System.Drawing.Size(208, 22); - cmdBrowse.Text = "Browse Library"; // // cmdApiDocs // @@ -175,25 +177,21 @@ namespace MediaBrowser.ServerApplication cmdGtihub}); cmdApiDocs.Name = "cmdApiDocs"; cmdApiDocs.Size = new System.Drawing.Size(208, 22); - cmdApiDocs.Text = "View Api Documentation"; // // cmdStandardDocs // cmdStandardDocs.Name = "cmdStandardDocs"; cmdStandardDocs.Size = new System.Drawing.Size(136, 22); - cmdStandardDocs.Text = "Standard"; // // cmdSwagger // cmdSwagger.Name = "cmdSwagger"; cmdSwagger.Size = new System.Drawing.Size(136, 22); - cmdSwagger.Text = "Swagger"; // // cmdGtihub // cmdGtihub.Name = "cmdGtihub"; cmdGtihub.Size = new System.Drawing.Size(136, 22); - cmdGtihub.Text = "Github Wiki"; cmdExit.Click += cmdExit_Click; cmdRestart.Click += cmdRestart_Click; @@ -211,6 +209,8 @@ namespace MediaBrowser.ServerApplication _logManager.LoggerLoaded += LoadLogWindow; _configurationManager.ConfigurationUpdated += Instance_ConfigurationUpdated; + LocalizeText(); + if (_appHost.IsFirstRun) { Action action = () => notifyIcon1.ShowBalloonTip(5000, "Media Browser", "Welcome to Media Browser Server!", ToolTipIcon.Info); @@ -219,6 +219,24 @@ namespace MediaBrowser.ServerApplication } } + private void LocalizeText() + { + _uiCulture = _configurationManager.Configuration.UICulture; + + cmdExit.Text = _localization.GetLocalizedString("LabelExit"); + cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity"); + cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki"); + cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger"); + cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard"); + cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation"); + cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary"); + cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser"); + cmdLibraryExplorer.Text = _localization.GetLocalizedString("LabelOpenLibraryViewer"); + cmdRestart.Text = _localization.GetLocalizedString("LabelRestartServer"); + cmdLogWindow.Text = _localization.GetLocalizedString("LabelShowLogWindow"); + } + + private string _uiCulture; /// <summary> /// Handles the ConfigurationUpdated event of the Instance control. /// </summary> @@ -226,6 +244,12 @@ namespace MediaBrowser.ServerApplication /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> void Instance_ConfigurationUpdated(object sender, EventArgs e) { + if (!string.Equals(_configurationManager.Configuration.UICulture, _uiCulture, + StringComparison.OrdinalIgnoreCase)) + { + LocalizeText(); + } + Action action = () => { var isLogWindowOpen = _logForm != null; @@ -307,7 +331,7 @@ namespace MediaBrowser.ServerApplication void cmdLibraryExplorer_Click(object sender, EventArgs e) { - new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _displayPreferencesManager, _itemRepository).Show(); + new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show(); } void cmdRestart_Click(object sender, EventArgs e) diff --git a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs index e9ba17bc1..ef3b79699 100644 --- a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs +++ b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs @@ -121,9 +121,9 @@ this.lblStatus.Location = new System.Drawing.Point(3, 0); this.lblStatus.MaximumSize = new System.Drawing.Size(0, 100); this.lblStatus.Name = "lblStatus"; - this.lblStatus.Size = new System.Drawing.Size(599, 59); + this.lblStatus.Size = new System.Drawing.Size(469, 59); this.lblStatus.TabIndex = 0; - this.lblStatus.Text = "Loading Media Browser Server"; + this.lblStatus.Text = "Loading Media Browser"; this.lblStatus.UseWaitCursor = true; // // panel1 diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 369fb2a07..597381529 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -15,6 +16,8 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using WebMarkupMin.Core.Minifiers; +using WebMarkupMin.Core.Settings; namespace MediaBrowser.WebDashboard.Api { @@ -96,6 +99,7 @@ namespace MediaBrowser.WebDashboard.Api private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="DashboardService" /> class. @@ -103,11 +107,12 @@ namespace MediaBrowser.WebDashboard.Api /// <param name="appHost">The app host.</param> /// <param name="serverConfigurationManager">The server configuration manager.</param> /// <param name="fileSystem">The file system.</param> - public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem) + public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; + _localization = localization; } /// <summary> @@ -148,7 +153,7 @@ namespace MediaBrowser.WebDashboard.Api { var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); - return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream())); + return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream(), null)); } /// <summary> @@ -210,13 +215,16 @@ namespace MediaBrowser.WebDashboard.Api var contentType = MimeTypes.GetMimeType(path); + var isHtml = IsHtml(path); + var localizationCulture = isHtml ? GetLocalizationCulture() : null; + // Don't cache if not configured to do so // But always cache images to simulate production - if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && - !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && + if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && + !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) { - return ResultFactory.GetResult(GetResourceStream(path).Result, contentType); + return ResultFactory.GetResult(GetResourceStream(path, isHtml, localizationCulture).Result, contentType); } TimeSpan? cacheDuration = null; @@ -230,17 +238,24 @@ namespace MediaBrowser.WebDashboard.Api var assembly = GetType().Assembly.GetName(); - var cacheKey = (assembly.Version + path).GetMD5(); + var cacheKey = (assembly.Version + (localizationCulture ?? string.Empty) + path).GetMD5(); - return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path)); + return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, isHtml, localizationCulture)); + } + + private string GetLocalizationCulture() + { + return _serverConfigurationManager.Configuration.UICulture; } /// <summary> /// Gets the resource stream. /// </summary> /// <param name="path">The path.</param> + /// <param name="isHtml">if set to <c>true</c> [is HTML].</param> + /// <param name="localizationCulture">The localization culture.</param> /// <returns>Task{Stream}.</returns> - private async Task<Stream> GetResourceStream(string path) + private async Task<Stream> GetResourceStream(string path, bool isHtml, string localizationCulture) { Stream resourceStream; @@ -259,13 +274,11 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null) { - var isHtml = IsHtml(path); - // Don't apply any caching for html pages // jQuery ajax doesn't seem to handle if-modified-since correctly if (isHtml) { - resourceStream = await ModifyHtml(resourceStream).ConfigureAwait(false); + resourceStream = await ModifyHtml(resourceStream, localizationCulture).ConfigureAwait(false); } } @@ -297,26 +310,48 @@ namespace MediaBrowser.WebDashboard.Api /// </summary> /// <param name="sourceStream">The source stream.</param> /// <returns>Task{Stream}.</returns> - internal async Task<Stream> ModifyHtml(Stream sourceStream) + private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture) { - string html; - - using (var memoryStream = new MemoryStream()) + using (sourceStream) { - await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); + string html; - html = Encoding.UTF8.GetString(memoryStream.ToArray()); - } + using (var memoryStream = new MemoryStream()) + { + await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); - var version = GetType().Assembly.GetName().Version; + html = Encoding.UTF8.GetString(memoryStream.ToArray()); - html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version)); + if (!string.IsNullOrWhiteSpace(localizationCulture)) + { + html = _localization.LocalizeDocument(html, localizationCulture, GetLocalizationToken); + } - var bytes = Encoding.UTF8.GetBytes(html); + try + { + var minifier = new HtmlMinifier(new HtmlMinificationSettings(true)); + + html = minifier.Minify(html).MinifiedContent; + } + catch (Exception ex) + { + Logger.ErrorException("Error minifying html", ex); + } + } - sourceStream.Dispose(); + var version = GetType().Assembly.GetName().Version; - return new MemoryStream(bytes); + html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version)); + + var bytes = Encoding.UTF8.GetBytes(html); + + return new MemoryStream(bytes); + } + } + + private string GetLocalizationToken(string phrase) + { + return "${" + phrase + "}"; } /// <summary> @@ -393,154 +428,200 @@ namespace MediaBrowser.WebDashboard.Api /// <returns>Task{Stream}.</returns> private async Task<Stream> GetAllJavascript() { - var scriptFiles = new[] - { - "extensions.js", - "site.js", - "librarybrowser.js", - "librarylist.js", - "editorsidebar.js", - "librarymenu.js", - "chromecast.js", - "contextmenu.js", - - "mediacontroller.js", - "mediaplayer.js", - "mediaplayer-video.js", - - "ratingdialog.js", - "aboutpage.js", - "allusersettings.js", - "alphapicker.js", - "addpluginpage.js", - "advancedconfigurationpage.js", - "advancedpaths.js", - "advancedserversettings.js", - "metadataadvanced.js", - "appsplayback.js", - "appsweather.js", - "autoorganizetv.js", - "autoorganizelog.js", - "channels.js", - "channelitems.js", - "dashboardinfo.js", - "dashboardpage.js", - "directorybrowser.js", - "dlnaprofile.js", - "dlnaprofiles.js", - "dlnasettings.js", - "editcollectionitems.js", - "edititemmetadata.js", - "edititempeople.js", - "edititemimages.js", - "encodingsettings.js", - "gamesrecommendedpage.js", - "gamesystemspage.js", - "gamespage.js", - "gamegenrepage.js", - "gamestudiospage.js", - "indexpage.js", - "itembynamedetailpage.js", - "itemdetailpage.js", - "itemgallery.js", - "itemlistpage.js", - "librarypathmapping.js", - "libraryreport.js", - "librarysettings.js", - "livetvchannel.js", - "livetvchannels.js", - "livetvguide.js", - "livetvnewrecording.js", - "livetvprogram.js", - "livetvrecording.js", - "livetvrecordinglist.js", - "livetvrecordings.js", - "livetvtimer.js", - "livetvseriestimer.js", - "livetvseriestimers.js", - "livetvsettings.js", - "livetvsuggested.js", - "livetvstatus.js", - "livetvtimers.js", - "loginpage.js", - "logpage.js", - "medialibrarypage.js", - "metadataconfigurationpage.js", - "metadataimagespage.js", - "moviegenres.js", - "moviecollections.js", - "movies.js", - "movieslatest.js", - "moviepeople.js", - "moviesrecommended.js", - "moviestudios.js", - "movietrailers.js", - "musicalbums.js", - "musicalbumartists.js", - "musicartists.js", - "musicgenres.js", - "musicrecommended.js", - "musicvideos.js", - "notifications.js", - "playlist.js", - "plugincatalogpage.js", - "pluginspage.js", - "pluginupdatespage.js", - "remotecontrol.js", - "scheduledtaskpage.js", - "scheduledtaskspage.js", - "search.js", - "songs.js", - "supporterkeypage.js", - "supporterpage.js", - "episodes.js", - "tvgenres.js", - "tvlatest.js", - "tvpeople.js", - "tvrecommended.js", - "tvshows.js", - "tvstudios.js", - "tvupcoming.js", - "useredit.js", - "userpassword.js", - "userimagepage.js", - "userprofilespage.js", - "usersettings.js", - "userparentalcontrol.js", - "wizardfinishpage.js", - "wizardimagesettings.js", - "wizardservice.js", - "wizardstartpage.js", - "wizardsettings.js", - "wizarduserpage.js" - }; - var memoryStream = new MemoryStream(); var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); + // jQuery + jQuery mobile await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.js", newLineBytes).ConfigureAwait(false); + await AppendLocalization(memoryStream).ConfigureAwait(false); + await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); + + // Write the version string for the dashboard comparison function var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion); var versionBytes = Encoding.UTF8.GetBytes(versionString); await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); - await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false); - + var builder = new StringBuilder(); var assembly = GetType().Assembly; - await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false); + using (var stream = assembly.GetManifestResourceStream("MediaBrowser.WebDashboard.ApiClient.js")) + { + using (var streamReader = new StreamReader(stream)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } + + foreach (var file in GetScriptFiles()) + { + var path = GetDashboardResourcePath("scripts/" + file); + + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + { + using (var streamReader = new StreamReader(fs)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } + } + + var js = builder.ToString(); + + try + { + var result = new CrockfordJsMinifier().Minify(js, false, Encoding.UTF8); - foreach (var file in scriptFiles) + js = result.MinifiedContent; + } + catch (Exception ex) { - await AppendResource(memoryStream, "scripts/" + file, newLineBytes).ConfigureAwait(false); + Logger.ErrorException("Error minifying javascript", ex); } + + var bytes = Encoding.UTF8.GetBytes(js); + await memoryStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); memoryStream.Position = 0; return memoryStream; } + private IEnumerable<string> GetScriptFiles() + { + return new[] + { + "extensions.js", + "site.js", + "librarybrowser.js", + "librarylist.js", + "editorsidebar.js", + "librarymenu.js", + "chromecast.js", + "contextmenu.js", + + "mediacontroller.js", + "mediaplayer.js", + "mediaplayer-video.js", + + "ratingdialog.js", + "aboutpage.js", + "allusersettings.js", + "alphapicker.js", + "addpluginpage.js", + "advancedconfigurationpage.js", + "advancedpaths.js", + "advancedserversettings.js", + "metadataadvanced.js", + "appsplayback.js", + "appsweather.js", + "autoorganizetv.js", + "autoorganizelog.js", + "channels.js", + "channelitems.js", + "dashboardinfo.js", + "dashboardpage.js", + "directorybrowser.js", + "dlnaprofile.js", + "dlnaprofiles.js", + "dlnasettings.js", + "editcollectionitems.js", + "edititemmetadata.js", + "edititempeople.js", + "edititemimages.js", + "encodingsettings.js", + "gamesrecommendedpage.js", + "gamesystemspage.js", + "gamespage.js", + "gamegenrepage.js", + "gamestudiospage.js", + "indexpage.js", + "itembynamedetailpage.js", + "itemdetailpage.js", + "itemgallery.js", + "itemlistpage.js", + "librarypathmapping.js", + "libraryreport.js", + "librarysettings.js", + "livetvchannel.js", + "livetvchannels.js", + "livetvguide.js", + "livetvnewrecording.js", + "livetvprogram.js", + "livetvrecording.js", + "livetvrecordinglist.js", + "livetvrecordings.js", + "livetvtimer.js", + "livetvseriestimer.js", + "livetvseriestimers.js", + "livetvsettings.js", + "livetvsuggested.js", + "livetvstatus.js", + "livetvtimers.js", + "loginpage.js", + "logpage.js", + "medialibrarypage.js", + "metadataconfigurationpage.js", + "metadataimagespage.js", + "moviegenres.js", + "moviecollections.js", + "movies.js", + "movieslatest.js", + "moviepeople.js", + "moviesrecommended.js", + "moviestudios.js", + "movietrailers.js", + "musicalbums.js", + "musicalbumartists.js", + "musicartists.js", + "musicgenres.js", + "musicrecommended.js", + "musicvideos.js", + "notifications.js", + "playlist.js", + "plugincatalogpage.js", + "pluginspage.js", + "pluginupdatespage.js", + "remotecontrol.js", + "scheduledtaskpage.js", + "scheduledtaskspage.js", + "search.js", + "songs.js", + "supporterkeypage.js", + "supporterpage.js", + "episodes.js", + "tvgenres.js", + "tvlatest.js", + "tvpeople.js", + "tvrecommended.js", + "tvshows.js", + "tvstudios.js", + "tvupcoming.js", + "useredit.js", + "userpassword.js", + "userimagepage.js", + "userprofilespage.js", + "usersettings.js", + "userparentalcontrol.js", + "wizardfinishpage.js", + "wizardimagesettings.js", + "wizardservice.js", + "wizardstartpage.js", + "wizardsettings.js", + "wizarduserpage.js" + }; + } + + private async Task AppendLocalization(Stream stream) + { + + } + /// <summary> /// Gets all CSS. /// </summary> @@ -568,35 +649,40 @@ namespace MediaBrowser.WebDashboard.Api "icons.css" }; - var memoryStream = new MemoryStream(); - - var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); + var builder = new StringBuilder(); foreach (var file in files) { - await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false); + var path = GetDashboardResourcePath("css/" + file); + + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + { + using (var streamReader = new StreamReader(fs)) + { + var text = await streamReader.ReadToEndAsync().ConfigureAwait(false); + builder.Append(text); + builder.Append(Environment.NewLine); + } + } } - - memoryStream.Position = 0; - return memoryStream; - } - /// <summary> - /// Appends the resource. - /// </summary> - /// <param name="assembly">The assembly.</param> - /// <param name="outputStream">The output stream.</param> - /// <param name="path">The path.</param> - /// <param name="newLineBytes">The new line bytes.</param> - /// <returns>Task.</returns> - private async Task AppendResource(Assembly assembly, Stream outputStream, string path, byte[] newLineBytes) - { - using (var stream = assembly.GetManifestResourceStream(path)) + var css = builder.ToString(); + + try { - await stream.CopyToAsync(outputStream).ConfigureAwait(false); + var result = new KristensenCssMinifier().Minify(builder.ToString(), false, Encoding.UTF8); - await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); + //css = result.MinifiedContent; + } + catch (Exception ex) + { + Logger.ErrorException("Error minifying css", ex); } + + var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(css)); + + memoryStream.Position = 0; + return memoryStream; } /// <summary> diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 6c6487cc2..211f216d5 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -57,6 +57,9 @@ <Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="WebMarkupMin.Core">
+ <HintPath>..\packages\WebMarkupMin.Core.0.8.18\lib\net40\WebMarkupMin.Core.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -641,9 +644,6 @@ <Content Include="dashboard-ui\livetvseriestimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1477,9 +1477,6 @@ <Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1970,6 +1967,7 @@ </Content>
</ItemGroup>
<ItemGroup>
+ <None Include="app.config" />
<None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -1983,6 +1981,9 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
+ <None Include="WebMarkupMin.Configuration.xsd">
+ <SubType>Designer</SubType>
+ </None>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/MediaBrowser.WebDashboard/app.config b/MediaBrowser.WebDashboard/app.config new file mode 100644 index 000000000..8c2c664cd --- /dev/null +++ b/MediaBrowser.WebDashboard/app.config @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <configSections> + <sectionGroup name="webMarkupMin"> + <section name="core" type="WebMarkupMin.Core.Configuration.CoreConfiguration, WebMarkupMin.Core" /> + </sectionGroup> + </configSections> + <webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd"> + <core> + <css> + <minifiers> + <add name="NullCssMinifier" displayName="Null CSS Minifier" type="WebMarkupMin.Core.Minifiers.NullCssMinifier, WebMarkupMin.Core" /> + <add name="KristensenCssMinifier" displayName="Mads Kristensen's CSS minifier" type="WebMarkupMin.Core.Minifiers.KristensenCssMinifier, WebMarkupMin.Core" /> + </minifiers> + </css> + <js> + <minifiers> + <add name="NullJsMinifier" displayName="Null JS Minifier" type="WebMarkupMin.Core.Minifiers.NullJsMinifier, WebMarkupMin.Core" /> + <add name="CrockfordJsMinifier" displayName="Douglas Crockford's JS Minifier" type="WebMarkupMin.Core.Minifiers.CrockfordJsMinifier, WebMarkupMin.Core" /> + </minifiers> + </js> + <logging> + <loggers> + <add name="NullLogger" displayName="Null Logger" type="WebMarkupMin.Core.Loggers.NullLogger, WebMarkupMin.Core" /> + <add name="ThrowExceptionLogger" displayName="Throw exception logger" type="WebMarkupMin.Core.Loggers.ThrowExceptionLogger, WebMarkupMin.Core" /> + </loggers> + </logging> + </core> + </webMarkupMin> +</configuration>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index a874d5f6f..b867f7a92 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" /> + <package id="WebMarkupMin.Core" version="0.8.18" targetFramework="net45" /> </packages>
\ No newline at end of file |
