diff --git a/src/Examples/Identity.MongoDB.API/Config/Localization/de-DE.json b/src/Examples/Identity.MongoDB.API/Config/Localization/de-DE.json new file mode 100644 index 0000000..faaa1c1 --- /dev/null +++ b/src/Examples/Identity.MongoDB.API/Config/Localization/de-DE.json @@ -0,0 +1,4 @@ +{ + "hi": "Hallo", + "welcome": "Willkommen {0}, wie geht es dir?" +} diff --git a/src/Examples/Identity.MongoDB.API/Config/Localization/en-US.json b/src/Examples/Identity.MongoDB.API/Config/Localization/en-US.json new file mode 100644 index 0000000..3ce7783 --- /dev/null +++ b/src/Examples/Identity.MongoDB.API/Config/Localization/en-US.json @@ -0,0 +1,4 @@ +{ + "hi": "Hello", + "welcome": "Welcome {0}, How are you?" +} \ No newline at end of file diff --git a/src/Examples/Identity.MongoDB.API/Controllers/LocalizationController.cs b/src/Examples/Identity.MongoDB.API/Controllers/LocalizationController.cs new file mode 100644 index 0000000..3e7cbad --- /dev/null +++ b/src/Examples/Identity.MongoDB.API/Controllers/LocalizationController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using uBeac.Localization; +using uBeac.Web; + +namespace Identity.MongoDB.API.Controllers; + +public class LocalizationController : BaseController +{ + private readonly IStringLocalizer _localizer; + private readonly ILocalizationService _localizationService; + + public LocalizationController(IStringLocalizer localizer, ILocalizationService localizationService) + { + _localizer = localizer; + _localizationService = localizationService; + } + + [HttpGet] + public IResult Test(string name = "Hesam") + { + return $"{_localizer["hi"]} {_localizer["welcome", name]}" + .ToResult(); + } + + [HttpPost] + public async Task Upsert(LocalizationValue value) + { + await _localizationService.Upsert(value); + } + + [HttpPost] + public async Task Delete(string key, string cultureName) + { + await _localizationService.Delete(key, cultureName); + } +} diff --git a/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj b/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj index 7139a45..f759f15 100644 --- a/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj +++ b/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/Examples/Identity.MongoDB.API/Program.cs b/src/Examples/Identity.MongoDB.API/Program.cs index 133eca4..eae0f99 100644 --- a/src/Examples/Identity.MongoDB.API/Program.cs +++ b/src/Examples/Identity.MongoDB.API/Program.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; +using uBeac.Localization; using uBeac.Repositories.History.MongoDB; using uBeac.Repositories.MongoDB; using uBeac.Web; @@ -17,6 +18,7 @@ // Adding http logging builder.Services.AddMongoDbHttpLogging("HttpLoggingConnection", builder.Configuration.GetInstance("HttpLogging")); +builder.Services.AddMemoryCache(); builder.Services.AddHttpContextAccessor(); builder.Services.AddControllers(); builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); @@ -43,6 +45,13 @@ builder.Services.AddMongo("HistoryConnection"); builder.Services.AddHistory().For(); +// Adding localization +builder.Services.AddCustomLocalization(localization => +{ + localization.UseJsonFiles(); + localization.UseInMemoryCaching(); +}); + // Adding CORS var corsPolicyOptions = builder.Configuration.GetSection("CorsPolicy"); builder.Services.AddCorsPolicy(corsPolicyOptions); diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/Entities/LocalizationValue.cs b/src/Localization/uBeac.Core.Localization.Abstractions/Entities/LocalizationValue.cs new file mode 100644 index 0000000..0d25dbd --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/Entities/LocalizationValue.cs @@ -0,0 +1,8 @@ +namespace uBeac.Localization; + +public class LocalizationValue : Entity +{ + public string Key { get; set; } // "welcome-message" + public string Value { get; set; } // "Welcome to uBeac!" + public string CultureName { get; set; } // "en-US" +} diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Builder.cs b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Builder.cs new file mode 100644 index 0000000..b103c5c --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Builder.cs @@ -0,0 +1,8 @@ +namespace uBeac.Localization; + +public interface ILocalizationBuilder +{ + ILocalizationBuilder SetRepository(Type repositoryType); + ILocalizationBuilder SetService(Type serviceType); + ILocalizationBuilder SetCachingService(Type cachingServiceType); +} diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/CachingService.cs b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/CachingService.cs new file mode 100644 index 0000000..bcdb32c --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/CachingService.cs @@ -0,0 +1,12 @@ +using uBeac.Services; + +namespace uBeac.Localization; + +public interface ILocalizationCachingService : IService +{ + void AddRange(IEnumerable values); + + IEnumerable GetAll(); + + void Clear(); +} diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Repository.cs b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Repository.cs new file mode 100644 index 0000000..4de8005 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Repository.cs @@ -0,0 +1,12 @@ +using uBeac.Repositories; + +namespace uBeac.Localization; + +public interface ILocalizationRepository : IRepository +{ + Task> GetAll(CancellationToken cancellationToken = default); + + Task Upsert(LocalizationValue entity, CancellationToken cancellationToken = default); + + Task Delete(string key, string cultureName, CancellationToken cancellationToken = default); +} diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Service.cs b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Service.cs new file mode 100644 index 0000000..943df41 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/Interfaces/Service.cs @@ -0,0 +1,16 @@ +using uBeac.Services; + +namespace uBeac.Localization; + +public interface ILocalizationService : IService +{ + Task> GetAllByCultureName(string cultureName, CancellationToken cancellationToken = default); + + Task ExistsValue(string key, string cultureName, CancellationToken cancellationToken = default); + + Task GetValue(string key, string cultureName, CancellationToken cancellationToken = default); + + Task Upsert(LocalizationValue entity, CancellationToken cancellationToken = default); + + Task Delete(string key, string cultureName, CancellationToken cancellationToken = default); +} diff --git a/src/Localization/uBeac.Core.Localization.Abstractions/uBeac.Core.Localization.Abstractions.csproj b/src/Localization/uBeac.Core.Localization.Abstractions/uBeac.Core.Localization.Abstractions.csproj new file mode 100644 index 0000000..dd67380 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Abstractions/uBeac.Core.Localization.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + disable + + + + + + + + + diff --git a/src/Localization/uBeac.Core.Localization.Repositories.Json/BuilderExtensions.cs b/src/Localization/uBeac.Core.Localization.Repositories.Json/BuilderExtensions.cs new file mode 100644 index 0000000..ed3c7d3 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.Json/BuilderExtensions.cs @@ -0,0 +1,13 @@ +using uBeac.Localization.Repositories.Json; + +namespace uBeac.Localization; + +public static class BuilderExtensions +{ + public static ILocalizationBuilder UseJsonFiles(this ILocalizationBuilder builder) + { + builder.SetRepository(typeof(JsonLocalizationRepository)); + + return builder; + } +} diff --git a/src/Localization/uBeac.Core.Localization.Repositories.Json/Options.cs b/src/Localization/uBeac.Core.Localization.Repositories.Json/Options.cs new file mode 100644 index 0000000..94d1000 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.Json/Options.cs @@ -0,0 +1,6 @@ +namespace uBeac.Localization.Repositories.Json; + +public class JsonLocalizationOptions +{ + public string FolderName { get; set; } = "Config\\Localization"; +} diff --git a/src/Localization/uBeac.Core.Localization.Repositories.Json/Repository.cs b/src/Localization/uBeac.Core.Localization.Repositories.Json/Repository.cs new file mode 100644 index 0000000..3271e06 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.Json/Repository.cs @@ -0,0 +1,99 @@ +using System.Text; +using Newtonsoft.Json; + +namespace uBeac.Localization.Repositories.Json; + +public interface IJsonLocalizationRepository : ILocalizationRepository +{ +} + +public class JsonLocalizationRepository : IJsonLocalizationRepository +{ + protected readonly string ContentRootPath; + protected readonly JsonLocalizationOptions Options; + + protected readonly JsonSerializerSettings SerializerSettings = new(); + + protected readonly string DirectoryPath; + + public JsonLocalizationRepository(string contentRootPath = null, JsonLocalizationOptions options = null) + { + ContentRootPath = contentRootPath ?? Environment.CurrentDirectory; + Options = options ?? new JsonLocalizationOptions(); + + DirectoryPath = Path.Combine(ContentRootPath, Options.FolderName); + } + + public async Task> GetAll(CancellationToken cancellationToken = default) + { + var result = new List(); + + foreach (var file in Directory.GetFiles(DirectoryPath)) + { + if (Path.GetExtension(file) != ".json") continue; + + var cultureName = Path.GetFileNameWithoutExtension(file); + var dictionary = ReadDictionaryFromCultureFile(cultureName); + var values = MapDictionaryToLocalizationValues(cultureName, dictionary); + + result.AddRange(values); + } + + return await Task.FromResult(result); + } + + public Task Upsert(LocalizationValue entity, CancellationToken cancellationToken = default) + { + var values = ReadDictionaryFromCultureFile(entity.CultureName); + + TryRemove(values, entity.Key); + + values.Add(entity.Key, entity.Value); + + WriteValuesToCultureFile(entity.CultureName, values); + + return Task.CompletedTask; + } + + public Task Delete(string key, string cultureName, CancellationToken cancellationToken = default) + { + var values = ReadDictionaryFromCultureFile(cultureName); + + TryRemove(values, key); + + WriteValuesToCultureFile(cultureName, values); + + return Task.CompletedTask; + } + + public string GetCultureFilePath(string cultureName) => Path.Combine(DirectoryPath, $"{cultureName}.json"); + + public IDictionary ReadDictionaryFromCultureFile(string cultureName) + { + var file = GetCultureFilePath(cultureName); + var fileContent = File.ReadAllText(file, Encoding.UTF8); + return JsonConvert.DeserializeObject>(fileContent, SerializerSettings); + } + + public void WriteValuesToCultureFile(string cultureName, IDictionary values) + { + var file = GetCultureFilePath(cultureName); + var fileContent = JsonConvert.SerializeObject(values, SerializerSettings); + File.WriteAllText(file, fileContent, Encoding.UTF8); + } + + public void TryRemove(IDictionary values, string key) + { + if (values.ContainsKey(key)) values.Remove(key); + } + + public IEnumerable MapDictionaryToLocalizationValues(string cultureName, IDictionary dictionary) + { + return dictionary.Select(x => new LocalizationValue + { + Key = x.Key, + Value = x.Value, + CultureName = cultureName + }); + } +} diff --git a/src/Localization/uBeac.Core.Localization.Repositories.Json/uBeac.Core.Localization.Repositories.Json.csproj b/src/Localization/uBeac.Core.Localization.Repositories.Json/uBeac.Core.Localization.Repositories.Json.csproj new file mode 100644 index 0000000..3fbec0b --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.Json/uBeac.Core.Localization.Repositories.Json.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + disable + + + + + + + + + + + diff --git a/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/BuilderExtensions.cs b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/BuilderExtensions.cs new file mode 100644 index 0000000..e3acb7d --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/BuilderExtensions.cs @@ -0,0 +1,14 @@ +using uBeac.Localization.Repositories.MongoDB; +using uBeac.Repositories.MongoDB; + +namespace uBeac.Localization; + +public static class BuilderExtensions +{ + public static ILocalizationBuilder UseMongoDB(this ILocalizationBuilder builder) where TContext : IMongoDBContext + { + builder.SetRepository(typeof(MongoDBLocalizationRepository)); + + return builder; + } +} diff --git a/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/Repository.cs b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/Repository.cs new file mode 100644 index 0000000..c804f48 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/Repository.cs @@ -0,0 +1,44 @@ +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using uBeac.Repositories.History; +using uBeac.Repositories.MongoDB; + +namespace uBeac.Localization.Repositories.MongoDB; + +public interface IMongoDBLocalizationRepository : ILocalizationRepository +{ +} + +public class MongoDBLocalizationRepository : MongoEntityRepository, IMongoDBLocalizationRepository + where TContext : IMongoDBContext +{ + public MongoDBLocalizationRepository(TContext mongoDbContext, IApplicationContext applicationContext, IHistoryManager history) : base(mongoDbContext, applicationContext, history) + { + } + + public async Task Upsert(LocalizationValue entity, CancellationToken cancellationToken = default) + { + var dbEntity = await GetByKey(entity.Key, entity.CultureName, cancellationToken); + + if (dbEntity == null) + { + await Create(entity, cancellationToken); + return; + } + + dbEntity.Value = entity.Value; + await Update(entity, cancellationToken); + } + + public async Task Delete(string key, string cultureName, CancellationToken cancellationToken = default) + { + var entity = await GetByKey(key, cultureName, cancellationToken); + await Delete(entity.Id, cancellationToken); + } + + public async Task GetByKey(string key, string cultureName, CancellationToken cancellationToken = default) + { + return await Collection.AsQueryable() + .FirstOrDefaultAsync(x => x.Key == key && x.CultureName == cultureName, cancellationToken); + } +} diff --git a/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/uBeac.Core.Localization.Repositories.MongoDB.csproj b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/uBeac.Core.Localization.Repositories.MongoDB.csproj new file mode 100644 index 0000000..f96eabf --- /dev/null +++ b/src/Localization/uBeac.Core.Localization.Repositories.MongoDB/uBeac.Core.Localization.Repositories.MongoDB.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + disable + + + + + + + + diff --git a/src/Localization/uBeac.Core.Localization/ServiceCollectionBuilder.cs b/src/Localization/uBeac.Core.Localization/ServiceCollectionBuilder.cs new file mode 100644 index 0000000..219fb28 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/ServiceCollectionBuilder.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace uBeac.Localization; + +public class ServiceCollectionLocalizationBuilder : ILocalizationBuilder +{ + protected readonly IServiceCollection Services; + + public ServiceCollectionLocalizationBuilder(IServiceCollection services) + { + Services = services; + } + + public ILocalizationBuilder SetRepository(Type repositoryType) + { + Services.TryAddScoped(typeof(ILocalizationRepository), repositoryType); + + return this; + } + + public ILocalizationBuilder SetService(Type serviceType) + { + Services.TryAddScoped(typeof(ILocalizationService), serviceType); + + return this; + } + + public ILocalizationBuilder SetCachingService(Type cachingServiceType) + { + Services.TryAddScoped(typeof(ILocalizationCachingService), cachingServiceType); + + return this; + } +} diff --git a/src/Localization/uBeac.Core.Localization/ServiceCollectionExtensions.cs b/src/Localization/uBeac.Core.Localization/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..d803bf4 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/ServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Localization; +using uBeac.Localization; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCustomLocalization(this IServiceCollection services, Action builder) + { + var serviceCollectionBuilder = new ServiceCollectionLocalizationBuilder(services); + + builder(serviceCollectionBuilder); + + serviceCollectionBuilder.SetService(typeof(LocalizationService)); + + services.AddScoped(); + + return services; + } + + public static ILocalizationBuilder UseInMemoryCaching(this ILocalizationBuilder services) + { + services.SetCachingService(typeof(InMemoryLocalizationCachingService)); + + return services; + } +} diff --git a/src/Localization/uBeac.Core.Localization/Services/LocalizationCachingService.cs b/src/Localization/uBeac.Core.Localization/Services/LocalizationCachingService.cs new file mode 100644 index 0000000..cdacf64 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/Services/LocalizationCachingService.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Caching.Memory; + +namespace uBeac.Localization; + +public class InMemoryLocalizationCachingService : ILocalizationCachingService +{ + private readonly IMemoryCache _memoryCache; + + protected const string CachingKey = "uBeac:LocalizationValues"; + + public InMemoryLocalizationCachingService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void AddRange(IEnumerable values) + { + _memoryCache.Set(CachingKey, values); + } + + public IEnumerable GetAll() + { + var exist = _memoryCache.TryGetValue>(CachingKey, out var values); + + return exist ? values : null; + } + + public void Clear() + { + _memoryCache.Remove(CachingKey); + } +} diff --git a/src/Localization/uBeac.Core.Localization/Services/LocalizationService.cs b/src/Localization/uBeac.Core.Localization/Services/LocalizationService.cs new file mode 100644 index 0000000..48fff55 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/Services/LocalizationService.cs @@ -0,0 +1,60 @@ +namespace uBeac.Localization; + +public class LocalizationService : ILocalizationService +{ + protected readonly ILocalizationRepository Repository; + protected readonly ILocalizationCachingService CachingService; + + public LocalizationService(ILocalizationRepository repository, ILocalizationCachingService cachingService) + { + Repository = repository; + CachingService = cachingService; + } + + public async Task> GetAll(CancellationToken cancellationToken = default) + { + var cacheValues = CachingService.GetAll(); + if (cacheValues != null) return cacheValues; + + var values = await Repository.GetAll(cancellationToken); + + new Thread(() => CachingService.AddRange(values)).Start(); + + return values; + } + + public async Task> GetAllByCultureName(string cultureName, CancellationToken cancellationToken = default) + { + var values = await GetAll(cancellationToken); + + return values.Where(x => x.CultureName == cultureName); + } + + public async Task ExistsValue(string key, string cultureName, CancellationToken cancellationToken = default) + { + var values = await GetAllByCultureName(cultureName, cancellationToken); + + return values.Any(x => x.Key == key); + } + + public async Task GetValue(string key, string cultureName, CancellationToken cancellationToken = default) + { + var values = await GetAllByCultureName(cultureName, cancellationToken); + + return values.FirstOrDefault(x => x.Key == key); + } + + public async Task Upsert(LocalizationValue entity, CancellationToken cancellationToken = default) + { + await Repository.Upsert(entity, cancellationToken); + + new Thread(() => CachingService.Clear()).Start(); + } + + public async Task Delete(string key, string cultureName, CancellationToken cancellationToken = default) + { + await Repository.Delete(key, cultureName, cancellationToken); + + new Thread(() => CachingService.Clear()).Start(); + } +} diff --git a/src/Localization/uBeac.Core.Localization/Services/Localizer.cs b/src/Localization/uBeac.Core.Localization/Services/Localizer.cs new file mode 100644 index 0000000..a402413 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/Services/Localizer.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Localization; + +namespace uBeac.Localization; + +public class Localizer : IStringLocalizer +{ + private readonly ILocalizationService _service; + private readonly IApplicationContext _context; + + public Localizer(ILocalizationService service, IApplicationContext context) + { + _service = service; + _context = context; + } + + public IEnumerable GetAllStrings(bool includeParentCultures) + { + var values = _service.GetAllByCultureName(_context.Language).Result; + + return values.Select(x => new LocalizedString(x.Key, x.Value, false)); + } + + public LocalizedString this[string name] + { + get + { + var exists = _service.ExistsValue(name, _context.Language).Result; + var value = exists ? _service.GetValue(name, _context.Language).Result.Value : name; + + return new LocalizedString(name, value, !exists); + } + } + + public LocalizedString this[string name, params object[] arguments] + { + get + { + var value = this[name]; + + if (value.ResourceNotFound) return value; + var formattedValue = string.Format(value.Value, arguments); + + return new LocalizedString(name, formattedValue, false); + } + } +} diff --git a/src/Localization/uBeac.Core.Localization/uBeac.Core.Localization.csproj b/src/Localization/uBeac.Core.Localization/uBeac.Core.Localization.csproj new file mode 100644 index 0000000..759b045 --- /dev/null +++ b/src/Localization/uBeac.Core.Localization/uBeac.Core.Localization.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + disable + + + + + + + + + + + + + diff --git a/src/uBeac.Core.sln b/src/uBeac.Core.sln index 661b9b2..77329fb 100644 --- a/src/uBeac.Core.sln +++ b/src/uBeac.Core.sln @@ -67,6 +67,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "uBeac.Core.Identity.Jwt", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{6D83110C-1361-45E1-A638-6BC37F0B968F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Localization", "Localization", "{EE87A38C-14CA-4138-9D76-1FC098040A4B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.Localization.Abstractions", "Localization\uBeac.Core.Localization.Abstractions\uBeac.Core.Localization.Abstractions.csproj", "{6BF8D20E-9ADB-41B0-A84C-0D6025C90496}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.Localization.Repositories.MongoDB", "Localization\uBeac.Core.Localization.Repositories.MongoDB\uBeac.Core.Localization.Repositories.MongoDB.csproj", "{C25E8E88-D406-458F-BBDC-C600F36922D3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.Localization.Repositories.Json", "Localization\uBeac.Core.Localization.Repositories.Json\uBeac.Core.Localization.Repositories.Json.csproj", "{66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.Localization", "Localization\uBeac.Core.Localization\uBeac.Core.Localization.csproj", "{1D857AC9-4E7F-4841-8E6D-94D6302FA345}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -169,6 +179,22 @@ Global {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Release|Any CPU.Build.0 = Release|Any CPU + {6BF8D20E-9ADB-41B0-A84C-0D6025C90496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BF8D20E-9ADB-41B0-A84C-0D6025C90496}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BF8D20E-9ADB-41B0-A84C-0D6025C90496}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BF8D20E-9ADB-41B0-A84C-0D6025C90496}.Release|Any CPU.Build.0 = Release|Any CPU + {C25E8E88-D406-458F-BBDC-C600F36922D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C25E8E88-D406-458F-BBDC-C600F36922D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C25E8E88-D406-458F-BBDC-C600F36922D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C25E8E88-D406-458F-BBDC-C600F36922D3}.Release|Any CPU.Build.0 = Release|Any CPU + {66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42}.Release|Any CPU.Build.0 = Release|Any CPU + {1D857AC9-4E7F-4841-8E6D-94D6302FA345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D857AC9-4E7F-4841-8E6D-94D6302FA345}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D857AC9-4E7F-4841-8E6D-94D6302FA345}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D857AC9-4E7F-4841-8E6D-94D6302FA345}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -198,6 +224,10 @@ Global {9D7D2782-2067-4B55-990B-ABF03939C1B3} = {EA4A3C91-FB93-49E8-8DF9-8CE86C150F71} {FAAE32CA-48F3-4F26-BF22-39DDDDF57770} = {6D83110C-1361-45E1-A638-6BC37F0B968F} {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0} = {EA4A3C91-FB93-49E8-8DF9-8CE86C150F71} + {6BF8D20E-9ADB-41B0-A84C-0D6025C90496} = {EE87A38C-14CA-4138-9D76-1FC098040A4B} + {C25E8E88-D406-458F-BBDC-C600F36922D3} = {EE87A38C-14CA-4138-9D76-1FC098040A4B} + {66E6F2EE-3EC3-47ED-A5FC-EE3F4F0A3F42} = {EE87A38C-14CA-4138-9D76-1FC098040A4B} + {1D857AC9-4E7F-4841-8E6D-94D6302FA345} = {EE87A38C-14CA-4138-9D76-1FC098040A4B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {247F9BF0-18BB-4A57-BB3E-2870DDAEF0D1}