Browse Source

Merge remote-tracking branch 'upstream/master'

Tim Hobbs 11 years ago
parent
commit
5693327a20
26 changed files with 698 additions and 221 deletions
  1. 15 0
      MediaBrowser.Api/LocalizationService.cs
  2. 1 11
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 1 1
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  4. 38 1
      MediaBrowser.Controller/Localization/ILocalizationManager.cs
  5. 4 0
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  6. 6 0
      MediaBrowser.Model/Globalization/CountryInfo.cs
  7. 6 2
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  8. 21 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
  9. 21 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  10. 21 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
  11. 114 3
      MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
  12. 17 0
      MediaBrowser.Server.Implementations/Localization/Server/de.json
  13. 17 0
      MediaBrowser.Server.Implementations/Localization/Server/en_US.json
  14. 17 0
      MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
  15. 17 0
      MediaBrowser.Server.Implementations/Localization/Server/ru.json
  16. 17 0
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  17. 9 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  18. 4 4
      MediaBrowser.ServerApplication/ApplicationHost.cs
  19. 2 4
      MediaBrowser.ServerApplication/LibraryViewer.cs
  20. 1 1
      MediaBrowser.ServerApplication/MainStartup.cs
  21. 37 13
      MediaBrowser.ServerApplication/ServerNotifyIcon.cs
  22. 2 2
      MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs
  23. 266 173
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  24. 13 6
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  25. 30 0
      MediaBrowser.WebDashboard/app.config
  26. 1 0
      MediaBrowser.WebDashboard/packages.config

+ 15 - 0
MediaBrowser.Api/LocalizationService.cs

@@ -31,6 +31,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>
@@ -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>

+ 1 - 11
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1242,19 +1242,9 @@ namespace MediaBrowser.Api.Playback
                 }
                 else if (i == 14)
                 {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.Framerate = int.Parse(val, UsCulture);
-                    }
+                    request.StartTimeTicks = long.Parse(val, UsCulture);
                 }
                 else if (i == 15)
-                {
-                    if (videoRequest != null)
-                    {
-                        request.StartTimeTicks = long.Parse(val, UsCulture);
-                    }
-                }
-                else if (i == 16)
                 {
                     if (videoRequest != null)
                     {

+ 1 - 1
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);
 

+ 38 - 1
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,42 @@ 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();
+
+        /// <summary>
+        /// Gets the java script localization dictionary.
+        /// </summary>
+        /// <param name="culture">The culture.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture);
     }
 }

+ 4 - 0
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";
         }
     }
 

+ 6 - 0
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; }
+    }
 }

+ 6 - 2
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -257,8 +257,12 @@ namespace MediaBrowser.Server.Implementations.IO
                         InternalBufferSize = 32767
                     };
 
-                    newWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName |
-                                              NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size;
+                    newWatcher.NotifyFilter = NotifyFilters.CreationTime |
+                        NotifyFilters.DirectoryName |
+                        NotifyFilters.FileName |
+                        NotifyFilters.LastWrite |
+                        NotifyFilters.Size |
+                        NotifyFilters.Attributes;
 
                     newWatcher.Created += watcher_Changed;
                     newWatcher.Deleted += watcher_Changed;

+ 21 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json

@@ -0,0 +1,21 @@
+{
+	"SettingsSaved": "Settings saved.",
+	"AddUser": "Add User",
+	"Users": "Users",
+	"Delete": "Delete",
+	"Administrator": "Administrator",
+	"Password": "Password",
+	"CreatePassword": "Create Password",
+	"DeleteImage": "Delete Image",
+	"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
+	"FileReadCancelled": "The file read has been cancelled.",
+	"FileNotFound": "File not found.",
+	"FileReadError": "An error occurred while reading the file.",
+	"DeleteUser": "Delete User",
+	"DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
+	"PasswordResetHeader": "Password Reset",
+	"PasswordResetComplete": "The password has been reset.",
+	"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+	"PasswordSaved": "Password saved.",
+	"PasswordMatchError": "Password and password confirmation must match."
+}

+ 21 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -0,0 +1,21 @@
+{
+    "SettingsSaved": "Settings saved.",
+	"AddUser": "Add User",
+	"Users": "Users",
+	"Delete": "Delete",
+	"Administrator": "Administrator",
+	"Password": "Password",
+	"CreatePassword": "Create Password",
+	"DeleteImage": "Delete Image",
+	"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
+	"FileReadCancelled": "The file read has been cancelled.",
+	"FileNotFound": "File not found.",
+	"FileReadError": "An error occurred while reading the file.",
+	"DeleteUser": "Delete User",
+	"DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
+	"PasswordResetHeader": "Password Reset",
+	"PasswordResetComplete": "The password has been reset.",
+	"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+	"PasswordSaved": "Password saved.",
+	"PasswordMatchError": "Password and password confirmation must match."
+}

+ 21 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json

@@ -0,0 +1,21 @@
+{
+	"SettingsSaved": "Configura\u00e7\u00f5es guardadas.",
+	"AddUser": "Adicionar Utilizador",
+	"Users": "Utilizadores",
+	"Delete": "Apagar",
+	"Administrator": "Administrador",
+	"Password": "Senha",
+	"CreatePassword": "Criar Senha",
+	"DeleteImage": "Apagar Imagem",
+	"DeleteImageConfirmation": "Tem a certeza que pretende apagar a imagem?",
+	"FileReadCancelled": "The file read has been cancelled.",
+	"FileNotFound": "Ficheiro n\u00e3o encontrado",
+	"FileReadError": "Ocorreu um erro ao ler o ficheiro.",
+	"DeleteUser": "Apagar Utilizador",
+	"DeleteUserConfirmation": "Tem a certeza que pretende apagar {0}?",
+	"PasswordResetHeader": "Password Reset",
+	"PasswordResetComplete": "The password has been reset.",
+	"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+	"PasswordSaved": "Senha guardada.",
+	"PasswordMatchError": "Password and password confirmation must match."
+}

+ 114 - 3
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,112 @@ 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"},
+                new LocalizatonOption{ Name="German", Value="de"},
+                new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
+                new LocalizatonOption{ Name="Russian", Value="ru"}
+
+            }.OrderBy(i => i.Name);
+        }
+
+        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;
+        }
     }
 }

+ 17 - 0
MediaBrowser.Server.Implementations/Localization/Server/de.json

@@ -0,0 +1,17 @@
+{
+	"LabelExit": "Ende",
+	"LabelVisitCommunity": "Besuche die Community",
+	"LabelGithubWiki": "Github Wiki",
+	"LabelSwagger": "Swagger",
+	"LabelStandard": "Standard",
+	"LabelViewApiDocumentation": "Zeige Api Dokumentation",
+	"LabelBrowseLibrary": "Durchsuche Bibliothek",
+	"LabelConfigureMediaBrowser": "Konfiguriere Media Browser",
+	"LabelOpenLibraryViewer": "\u00d6ffne Bibliothekenansicht",
+	"LabelRestartServer": "Server neustarten",
+	"LabelShowLogWindow": "Zeige Log Fenster",
+	"LabelPrevious": "Vorheriges",
+	"LabelFinish": "Ende",
+	"LabelNext": "N\u00e4chstes",
+	"LabelYoureDone": "Du bist fertig!"
+}

+ 17 - 0
MediaBrowser.Server.Implementations/Localization/Server/en_US.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!"
+}

+ 17 - 0
MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json

@@ -0,0 +1,17 @@
+{
+	"LabelExit": "Sair",
+	"LabelVisitCommunity": "Visitar a Comunidade",
+	"LabelGithubWiki": "Wiki Github",
+	"LabelSwagger": "Swagger",
+	"LabelStandard": "Padr\u00e3o",
+	"LabelViewApiDocumentation": "Ver Documenta\u00e7\u00e3o da API",
+	"LabelBrowseLibrary": "Navegar na Biblioteca",
+	"LabelConfigureMediaBrowser": "Configurar Media Browser",
+	"LabelOpenLibraryViewer": "Abrir Visualizador da Biblioteca",
+	"LabelRestartServer": "Reiniciar Servidor",
+	"LabelShowLogWindow": "Mostrar Janela de Log",
+	"LabelPrevious": "Anterior",
+	"LabelFinish": "Terminar",
+	"LabelNext": "Seguinte",
+	"LabelYoureDone": "Concluiu!"
+}

+ 17 - 0
MediaBrowser.Server.Implementations/Localization/Server/ru.json

@@ -0,0 +1,17 @@
+{
+	"LabelExit": "\u0412\u044b\u0445\u043e\u0434",
+	"LabelVisitCommunity": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c \u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e",
+	"LabelGithubWiki": "\u0412\u0438\u043a\u0438 \u043d\u0430 Github",
+	"LabelSwagger": "\u041e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u0435",
+	"LabelStandard": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439",
+	"LabelViewApiDocumentation": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u043e API",
+	"LabelBrowseLibrary": "\u041e\u0431\u043e\u0437\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
+	"LabelConfigureMediaBrowser": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Media Browser",
+	"LabelOpenLibraryViewer": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
+	"LabelRestartServer": "\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440",
+	"LabelShowLogWindow": "\u041e\u043a\u043d\u043e \u0416\u0443\u0440\u043d\u0430\u043b\u0430",
+	"LabelPrevious": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435",
+	"LabelFinish": "\u0417\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c",
+	"LabelNext": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435",
+	"LabelYoureDone": "\u0412\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0438!"
+}

+ 17 - 0
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!"
+}

+ 9 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -282,6 +282,14 @@
     <EmbeddedResource Include="Localization\Ratings\ru.txt" />
   </ItemGroup>
   <ItemGroup>
+    <EmbeddedResource Include="Localization\JavaScript\javascript.json" />
+    <EmbeddedResource Include="Localization\Server\server.json" />
+    <EmbeddedResource Include="Localization\Server\de.json" />
+    <EmbeddedResource Include="Localization\Server\pt_PT.json" />
+    <EmbeddedResource Include="Localization\Server\ru.json" />
+    <EmbeddedResource Include="Localization\JavaScript\en_US.json" />
+    <EmbeddedResource Include="Localization\JavaScript\pt_PT.json" />
+    <EmbeddedResource Include="Localization\Server\en_US.json" />
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
@@ -386,6 +394,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. 

+ 4 - 4
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));
 

+ 2 - 4
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));
 

+ 1 - 1
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();
         }
 

+ 37 - 13
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)

+ 2 - 2
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

+ 266 - 173
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -3,9 +3,11 @@ 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;
+using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using ServiceStack.Web;
 using System;
@@ -15,6 +17,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 +100,8 @@ namespace MediaBrowser.WebDashboard.Api
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
         private readonly IFileSystem _fileSystem;
+        private readonly ILocalizationManager _localization;
+        private readonly IJsonSerializer _jsonSerializer;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DashboardService" /> class.
@@ -103,11 +109,13 @@ 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, IJsonSerializer jsonSerializer)
         {
             _appHost = appHost;
             _serverConfigurationManager = serverConfigurationManager;
             _fileSystem = fileSystem;
+            _localization = localization;
+            _jsonSerializer = jsonSerializer;
         }
 
         /// <summary>
@@ -148,7 +156,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 +218,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 +241,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 +277,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 +313,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);
+
+                    html = Encoding.UTF8.GetString(memoryStream.ToArray());
 
-            var version = GetType().Assembly.GetName().Version;
+                    if (!string.IsNullOrWhiteSpace(localizationCulture))
+                    {
+                        html = _localization.LocalizeDocument(html, localizationCulture, GetLocalizationToken);
+                    }
 
-            html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version));
+                    try
+                    {
+                        var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
 
-            var bytes = Encoding.UTF8.GetBytes(html);
+                        html = minifier.Minify(html).MinifiedContent;
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.ErrorException("Error minifying html", ex);
+                    }
+                }
+
+                var version = GetType().Assembly.GetName().Version;
+
+                html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version));
 
-            sourceStream.Dispose();
+                var bytes = Encoding.UTF8.GetBytes(html);
 
-            return new MemoryStream(bytes);
+                return new MemoryStream(bytes);
+            }
+        }
+
+        private string GetLocalizationToken(string phrase)
+        {
+            return "${" + phrase + "}";
         }
 
         /// <summary>
@@ -393,154 +431,204 @@ 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",
+                                "dashboardgeneral.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)
+        {
+            var js = "window.localizationGlossary=" + _jsonSerializer.SerializeToString(_localization.GetJavaScriptLocalizationDictionary(GetLocalizationCulture()));
+
+            var bytes = Encoding.UTF8.GetBytes(js);
+            await stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+        }
+
         /// <summary>
         /// Gets all CSS.
         /// </summary>
@@ -568,35 +656,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))
-            {
-                await stream.CopyToAsync(outputStream).ConfigureAwait(false);
+            var css = builder.ToString();
 
-                await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
-            }
+            //try
+            //{
+            //    var result = new KristensenCssMinifier().Minify(builder.ToString(), false, Encoding.UTF8);
+
+            //    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>

+ 13 - 6
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">
@@ -221,6 +224,9 @@
     <Content Include="dashboard-ui\css\mediaplayer.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\dashboardgeneral.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\dashboardinfopage.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -515,6 +521,9 @@
     <Content Include="dashboard-ui\scripts\contextmenu.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\dashboardinfo.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -641,9 +650,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 +1483,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 +1973,7 @@
     </Content>
   </ItemGroup>
   <ItemGroup>
+    <None Include="app.config" />
     <None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
@@ -1983,6 +1987,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" />

+ 30 - 0
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>

+ 1 - 0
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>