From abaebd37d97efe7014e27b05491c6bbcf7c61437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 19 Dec 2017 15:43:50 +0300 Subject: [PATCH] Resolved #176: Allow to change localization and see the result without recompile on development time. --- .../Localization/AbpStringLocalizerFactory.cs | 8 +-- .../Localization/ILocalizationDictionary.cs | 2 + .../ILocalizationDictionaryProvider.cs | 5 +- .../Localization/LocalizationDictionary.cs | 7 ++- .../LocalizationDictionaryProviderBase.cs | 8 +++ .../Abp/Localization/LocalizationResource.cs | 27 +++++++++- ...eddedFileLocalizationDictionaryProvider.cs | 2 +- ...lFileLocalizationDictionaryProviderBase.cs | 52 ++++++++++++++----- .../VirtualFileSystem/VirtualFileProvider.cs | 2 +- 9 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs index aacd307054..1c2328c616 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs @@ -20,13 +20,14 @@ namespace Volo.Abp.Localization //TODO: It's better to use decorator pattern for IStringLocalizerFactory instead of getting ResourceManagerStringLocalizerFactory as a dependency. public AbpStringLocalizerFactory( ResourceManagerStringLocalizerFactory innerFactory, - IOptions abpLocalizationOptions, IServiceProvider serviceProvider) + IOptions abpLocalizationOptions, + IServiceProvider serviceProvider) { _innerFactory = innerFactory; _serviceProvider = serviceProvider; _abpLocalizationOptions = abpLocalizationOptions.Value; - _localizerCache = new ConcurrentDictionary();; + _localizerCache = new ConcurrentDictionary(); } public virtual IStringLocalizer Create(Type resourceType) @@ -42,7 +43,8 @@ namespace Volo.Abp.Localization private AbpDictionaryBasedStringLocalizer CreateAbpStringLocalizer(LocalizationResource resource) { - resource.Initialize(_serviceProvider); + resource.Initialize(_serviceProvider); //TODO: Use CreateScope? + return new AbpDictionaryBasedStringLocalizer( resource, resource.BaseResourceTypes.Select(Create).ToList() diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs index 4f0b980428..dd545fe163 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs @@ -29,5 +29,7 @@ namespace Volo.Abp.Localization IReadOnlyList GetAllStrings(); void Extend(ILocalizationDictionary dictionary); + + void Clear(); } } \ No newline at end of file diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs index ba2877cb18..be06f4dd88 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionaryProvider.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Volo.Abp.Localization { @@ -6,6 +7,8 @@ namespace Volo.Abp.Localization { IDictionary Dictionaries { get; } + event EventHandler Updated; + void Initialize(LocalizationResourceInitializationContext context); void Extend(ILocalizationDictionaryProvider dictionaryProvider); diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionary.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionary.cs index dbf5abec5e..1341a30e2f 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionary.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionary.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.Localization { /// public string CultureName { get; } - + /// public virtual LocalString this[string name] { @@ -51,6 +51,11 @@ namespace Volo.Abp.Localization } } + public void Clear() + { + _dictionary.Clear(); + } + /// public virtual IEnumerator GetEnumerator() { diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs index a4b94b71d2..28bcd22625 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationDictionaryProviderBase.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Volo.Abp.Localization @@ -6,6 +7,8 @@ namespace Volo.Abp.Localization { public IDictionary Dictionaries { get; } + public event EventHandler Updated; + protected LocalizationDictionaryProviderBase() { Dictionaries = new Dictionary(); @@ -35,5 +38,10 @@ namespace Volo.Abp.Localization existingDictionary.Extend(dictionary); } } + + protected virtual void OnUpdated() + { + Updated.InvokeSafely(this); + } } } \ No newline at end of file diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs index d4f81ef442..82f876d6e0 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs @@ -38,10 +38,30 @@ namespace Volo.Abp.Localization public virtual void Initialize(IServiceProvider serviceProvider) { + //TODO: We should refactor here to create a better design! + var context = new LocalizationResourceInitializationContext(serviceProvider); - DictionaryProvider.Initialize(context); + InitializeDictionaryProvider(context); + InitializeExtensions(context); + + DictionaryProvider.Updated += (sender, args) => + { + InitializeExtensions(context); + }; + foreach (var extension in Extensions) + { + extension.Updated += (sender, args) => + { + InitializeDictionaryProvider(context); + InitializeExtensions(context); + }; + } + } + + private void InitializeExtensions(LocalizationResourceInitializationContext context) + { foreach (var extension in Extensions) { extension.Initialize(context); @@ -49,6 +69,11 @@ namespace Volo.Abp.Localization } } + private void InitializeDictionaryProvider(LocalizationResourceInitializationContext context) + { + DictionaryProvider.Initialize(context); + } + protected virtual void AddBaseResourceTypes() { var descriptors = ResourceType diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs index 557da08a3f..9a9c92aa07 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/Json/JsonEmbeddedFileLocalizationDictionaryProvider.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.Localization.VirtualFiles.Json return file.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase); } - protected override ILocalizationDictionary CreateDictionary(string jsonString) + protected override ILocalizationDictionary CreateDictionaryFromFileContent(string jsonString) { return JsonLocalizationDictionaryBuilder.BuildFromJsonString(jsonString); //TODO: Use composition over inheritance! } diff --git a/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationDictionaryProviderBase.cs b/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationDictionaryProviderBase.cs index fd712331ab..de469bd213 100644 --- a/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationDictionaryProviderBase.cs +++ b/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationDictionaryProviderBase.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; using Volo.Abp.Internal; using Volo.Abp.VirtualFileSystem; @@ -9,6 +11,8 @@ namespace Volo.Abp.Localization.VirtualFiles { private readonly string _virtualPath; + private bool _subscribedForChanges; + protected VirtualFileLocalizationDictionaryProviderBase(string virtualPath) { _virtualPath = virtualPath; @@ -17,32 +21,52 @@ namespace Volo.Abp.Localization.VirtualFiles public override void Initialize(LocalizationResourceInitializationContext context) //TODO: Extract initialization to a factory..? { var virtualFileProvider = context.ServiceProvider.GetRequiredService(); - var directoryContents = virtualFileProvider.GetDirectoryContents(_virtualPath); - foreach (var file in directoryContents) + CreateDictionaries(virtualFileProvider); + + if (!_subscribedForChanges) + { + ChangeToken.OnChange(() => virtualFileProvider.Watch(_virtualPath.EnsureEndsWith('/') + "**/*.*"), () => + { + CreateDictionaries(virtualFileProvider); + OnUpdated(); + }); + + _subscribedForChanges = true; + } + } + + private void CreateDictionaries(IFileProvider fileProvider) + { + Dictionaries.Clear(); + + foreach (var file in fileProvider.GetDirectoryContents(_virtualPath)) { if (file.IsDirectory || !CanParseFile(file)) { continue; } - using (var stream = file.CreateReadStream()) + var dictionary = CreateDictionaryFromFile(file); + if (Dictionaries.ContainsKey(dictionary.CultureName)) { - var fileContent = Utf8Helper.ReadStringFromStream(stream); - - var dictionary = CreateDictionary(fileContent); - if (Dictionaries.ContainsKey(dictionary.CultureName)) - { - throw new AbpException($"{file.PhysicalPath} dictionary has a culture name '{dictionary.CultureName}' which is already defined!"); - } - - Dictionaries[dictionary.CultureName] = dictionary; + throw new AbpException($"{file.PhysicalPath} dictionary has a culture name '{dictionary.CultureName}' which is already defined!"); } + + Dictionaries[dictionary.CultureName] = dictionary; } } protected abstract bool CanParseFile(IFileInfo file); - protected abstract ILocalizationDictionary CreateDictionary(string fileContent); + protected virtual ILocalizationDictionary CreateDictionaryFromFile(IFileInfo file) + { + using (var stream = file.CreateReadStream()) + { + return CreateDictionaryFromFileContent(Utf8Helper.ReadStringFromStream(stream)); + } + } + + protected abstract ILocalizationDictionary CreateDictionaryFromFileContent(string fileContent); } } \ No newline at end of file diff --git a/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs b/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs index 00093c707f..5d58326128 100644 --- a/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs +++ b/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFileProvider.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.VirtualFileSystem if (_options.FileSets.PhysicalPaths.Any()) { var fileProviders = _options.FileSets.PhysicalPaths - .Select(p => new PhysicalFileProvider(p)) + .Select(rootPath => new PhysicalFileProvider(rootPath)) .Cast() .ToList();