From 384f74ab96eec1857134e551970e89939f4d3b66 Mon Sep 17 00:00:00 2001 From: windy liu Date: Sat, 9 Oct 2021 17:29:19 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Localization/JsonLocalizationFactory.cs | 24 +++++ Server/Localization/JsonStringLocalizer.cs | 100 ++++++++++++++++++ Server/Localization/LocalizationMiddleware.cs | 47 ++++++++ Server/Pages/About.razor | 13 +-- Server/Pages/ApiKeys.razor | 47 ++++---- Server/Pages/Branding.razor | 33 +++--- Server/Pages/Index.razor | 6 +- Server/Pages/_Host.cshtml | 5 +- Server/Resources/en-US.json | 40 +++++++ Server/Resources/zh-Hans.json | 9 ++ Server/Startup.cs | 21 +++- Server/_Imports.razor | 3 +- 12 files changed, 298 insertions(+), 50 deletions(-) create mode 100644 Server/Localization/JsonLocalizationFactory.cs create mode 100644 Server/Localization/JsonStringLocalizer.cs create mode 100644 Server/Localization/LocalizationMiddleware.cs create mode 100644 Server/Resources/en-US.json create mode 100644 Server/Resources/zh-Hans.json diff --git a/Server/Localization/JsonLocalizationFactory.cs b/Server/Localization/JsonLocalizationFactory.cs new file mode 100644 index 000000000..a4ed02f28 --- /dev/null +++ b/Server/Localization/JsonLocalizationFactory.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Localization; +using System; +using System.IO; +using System.Reflection; + +namespace Remotely.Server.Localization +{ + public class JsonStringLocalizerFactory : IStringLocalizerFactory + { + private readonly IDistributedCache _cache; + + public JsonStringLocalizerFactory(IDistributedCache cache) + { + _cache = cache; + } + + public IStringLocalizer Create(Type resourceSource) => + new JsonStringLocalizer(_cache); + + public IStringLocalizer Create(string baseName, string location) => + new JsonStringLocalizer(_cache); + } +} diff --git a/Server/Localization/JsonStringLocalizer.cs b/Server/Localization/JsonStringLocalizer.cs new file mode 100644 index 000000000..715dbe384 --- /dev/null +++ b/Server/Localization/JsonStringLocalizer.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Localization; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Remotely.Server.Localization +{ + public class JsonStringLocalizer : IStringLocalizer + { + + private readonly IDistributedCache _cache; + + private readonly JsonSerializer _serializer = new JsonSerializer(); + + public JsonStringLocalizer(IDistributedCache cache) + { + _cache = cache; + } + + public LocalizedString this[string name] + { + get + { + string value = GetString(name); + return new LocalizedString(name, value ?? name, value == null); + } + } + + public LocalizedString this[string name, params object[] arguments] + { + get + { + var actualValue = this[name]; + return !actualValue.ResourceNotFound + ? new LocalizedString(name, string.Format(actualValue.Value, arguments), false) + : actualValue; + } + } + + public IEnumerable GetAllStrings(bool includeParentCultures) + { + string filePath = $"Resources/{Thread.CurrentThread.CurrentCulture.Name}.json"; + using (var str = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var sReader = new StreamReader(str)) + using (var reader = new JsonTextReader(sReader)) + { + while (reader.Read()) + { + if (reader.TokenType != JsonToken.PropertyName) + continue; + string key = (string)reader.Value; + reader.Read(); + string value = _serializer.Deserialize(reader); + yield return new LocalizedString(key, value, false); + } + } + } + + private string GetString(string key) + { + string relativeFilePath = $"Resources/{Thread.CurrentThread.CurrentCulture.Name}.json"; + string fullFilePath = Path.GetFullPath(relativeFilePath); + string cacheKey = $"locale_{Thread.CurrentThread.CurrentCulture.Name}_{key}"; + string cacheValue = _cache.GetString(cacheKey); + if (!string.IsNullOrEmpty(cacheValue)) return cacheValue; + if (File.Exists(fullFilePath)) + { + + string result = GetValueFromJSON(key, Path.GetFullPath(relativeFilePath)); + if (!string.IsNullOrEmpty(result)) _cache.SetString(cacheKey, result); + return result; + } + + return default(string); + } + + private string GetValueFromJSON(string propertyName, string filePath) + { + if (propertyName == null) return default; + if (filePath == null) return default; + using (var str = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var sReader = new StreamReader(str)) + using (var reader = new JsonTextReader(sReader)) + { + while (reader.Read()) + { + if (reader.TokenType == JsonToken.PropertyName && (string)reader.Value == propertyName) + { + reader.Read(); + return _serializer.Deserialize(reader); + } + } + + return default; + } + } + } +} diff --git a/Server/Localization/LocalizationMiddleware.cs b/Server/Localization/LocalizationMiddleware.cs new file mode 100644 index 000000000..8670b4c55 --- /dev/null +++ b/Server/Localization/LocalizationMiddleware.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Remotely.Server.Localization +{ + public class LocalizationMiddleware : IMiddleware + { + private readonly IOptions _locOptions; + public LocalizationMiddleware(IOptions locOptions) + { + _locOptions = locOptions; + } + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var cultureKey = context.Request.Headers["Accept-Language"]; + if (!string.IsNullOrEmpty(cultureKey)) + { + if (DoesCultureExist(cultureKey)) + { + var culture = new System.Globalization.CultureInfo(cultureKey); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + } + else + { + var culture = _locOptions.Value.DefaultRequestCulture.Culture; + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + } + } + + await next(context); + } + + private bool DoesCultureExist(string cultureName) + { + return _locOptions.Value.SupportedCultures.Any(culture => string.Equals(culture.Name, cultureName, StringComparison.CurrentCultureIgnoreCase)); + + } + } +} diff --git a/Server/Pages/About.razor b/Server/Pages/About.razor index 7df1b604b..f6d66d41a 100644 --- a/Server/Pages/About.razor +++ b/Server/Pages/About.razor @@ -1,18 +1,19 @@ @page "/about" - -

About Remotely

+@using Microsoft.Extensions.Localization +@inject IStringLocalizer Localizer +

@Localizer["About"] Remotely

- Website: https://remotely.one + @Localizer["Website"]: https://remotely.one

- Contact: https://remotely.one/Contact + @Localizer["Contact"]: https://remotely.one/Contact

- Open-Source Licenses: Credits + @Localizer["Open-Source Licenses"]: Credits

- Version: @if (System.IO.File.Exists("Remotely_Server.dll")) + @Localizer["Version"]: @if (System.IO.File.Exists("Remotely_Server.dll")) { @System.Diagnostics.FileVersionInfo.GetVersionInfo("Remotely_Server.dll").FileVersion.ToString() } diff --git a/Server/Pages/ApiKeys.razor b/Server/Pages/ApiKeys.razor index a3d1c0754..10f7d8f1d 100644 --- a/Server/Pages/ApiKeys.razor +++ b/Server/Pages/ApiKeys.razor @@ -6,9 +6,10 @@ @inject AuthenticationStateProvider AuthProvider @inject IJsInterop JsInterop @inject IModalService ModalService +@using Microsoft.Extensions.Localization +@inject IStringLocalizer Localizer - -

API Keys

+

@Localizer["API Keys"]

@if (!string.IsNullOrWhiteSpace(_alertMessage)) { @@ -19,7 +20,7 @@ { if (!string.IsNullOrWhiteSpace(_newKeySecret)) { -
Warning: The key's secret will only be shown once. Save it now.
+
@Localizer["ApiKeyWarn"]
@@ -28,12 +29,12 @@
- + + class="form-control form-control-sm custom-control-inline mr-1" + style="width:200px" /> - +