From 3e18d79fa55906d93245a46dcc277ad7ccf51147 Mon Sep 17 00:00:00 2001 From: jvyden Date: Thu, 14 Apr 2022 16:24:17 -0400 Subject: [PATCH] Add translation support to website, read user's language from settings --- .../LocalizationManager.cs | 27 +++++++-------- ProjectLighthouse.Localization/Program.cs | 2 +- .../StringLists/BaseLayoutStrings.cs | 11 ++++++ .../TranslatableString.cs | 21 ++++++++++++ .../Pages/Layouts/BaseLayout.cshtml | 15 ++++---- .../Pages/Layouts/BaseLayout.cshtml.cs | 34 ++++++++++++++----- ProjectLighthouse/Startup/Startup.cs | 20 +++++++++++ ProjectLighthouse/Types/PageNavigationItem.cs | 6 ++-- 8 files changed, 103 insertions(+), 33 deletions(-) create mode 100644 ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs create mode 100644 ProjectLighthouse.Localization/TranslatableString.cs diff --git a/ProjectLighthouse.Localization/LocalizationManager.cs b/ProjectLighthouse.Localization/LocalizationManager.cs index 1f3a2d30..9401213b 100644 --- a/ProjectLighthouse.Localization/LocalizationManager.cs +++ b/ProjectLighthouse.Localization/LocalizationManager.cs @@ -15,15 +15,12 @@ public static class LocalizationManager Console.WriteLine($"Attempting to load '{key}' for '{language}'"); #endif - string resourceBasename; - if (language == defaultLang) - { - resourceBasename = $"{namespaceStr}.{translationArea.ToString()}"; - } - else - { - resourceBasename = $"{namespaceStr}.{translationArea.ToString()}.lang-{language}"; - } + string resourceBasename = $"{namespaceStr}.{translationArea.ToString()}"; + + // We don't have an en-US .resx, so if we aren't using en-US then we need to add the appropriate language. + // Otherwise, keep it to the normal .resx file + // e.g. BaseLayout.resx as opposed to BaseLayout.lang-da-DK.resx. + if (language != defaultLang) resourceBasename += $".lang-{language}"; ResourceManager resourceManager = new(resourceBasename, Assembly.GetExecutingAssembly()); @@ -39,12 +36,12 @@ public static class LocalizationManager return localizedString; } - public static IEnumerable GetAvailableLanguages(TranslationAreas translationArea) + // This is a bit scuffed, but it will work for what I need it to do. + public static IEnumerable GetAvailableLanguages() { - string area = translationArea.ToString(); + string area = TranslationAreas.BaseLayout.ToString(); - // scuffed but it will work for now - List langs = Assembly.GetExecutingAssembly() + List languages = Assembly.GetExecutingAssembly() .GetManifestResourceNames() .Where(r => r.StartsWith($"{namespaceStr}.{area}")) .Select(r => r.Substring(r.IndexOf(area), r.Length - r.IndexOf(area)).Substring(area.Length + 1)) @@ -53,8 +50,8 @@ public static class LocalizationManager .Where(r => r != "resources") .ToList(); - langs.Add(defaultLang); + languages.Add(defaultLang); - return langs; + return languages; } } \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Program.cs b/ProjectLighthouse.Localization/Program.cs index 45af94c6..a4603129 100644 --- a/ProjectLighthouse.Localization/Program.cs +++ b/ProjectLighthouse.Localization/Program.cs @@ -14,7 +14,7 @@ public static class Program Console.Write('\n'); - foreach (string language in LocalizationManager.GetAvailableLanguages(TranslationAreas.BaseLayout)) + foreach (string language in LocalizationManager.GetAvailableLanguages()) { Console.WriteLine(LocalizationManager.GetLocalizedString(TranslationAreas.BaseLayout, language, "header_home")); } diff --git a/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs new file mode 100644 index 00000000..2b396ce6 --- /dev/null +++ b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs @@ -0,0 +1,11 @@ +namespace LBPUnion.ProjectLighthouse.Localization.StringLists; + +public static class BaseLayoutStrings +{ + public static readonly TranslatableString HeaderHome = create("header_home"); + public static readonly TranslatableString HeaderUsers = create("header_users"); + public static readonly TranslatableString HeaderPhotos = create("header_photos"); + public static readonly TranslatableString HeaderSlots = create("header_slots"); + + private static TranslatableString create(string key) => new(TranslationAreas.BaseLayout, key); +} \ No newline at end of file diff --git a/ProjectLighthouse.Localization/TranslatableString.cs b/ProjectLighthouse.Localization/TranslatableString.cs new file mode 100644 index 00000000..4805a2ab --- /dev/null +++ b/ProjectLighthouse.Localization/TranslatableString.cs @@ -0,0 +1,21 @@ +namespace LBPUnion.ProjectLighthouse.Localization; + +public class TranslatableString +{ + public TranslatableString(TranslationAreas area, string key) + { + this.Key = key; + this.Area = area; + } + + public string Key { get; init; } + public TranslationAreas Area { get; init; } + + public string Translate(string language) => LocalizationManager.GetLocalizedString(this.Area, language, this.Key); + + [Obsolete("Do not translate by using ToString. Use TranslatableString.Translate().", true)] + public override string ToString() => "NOT TRANSLATED CORRECTLY!"; + + [Obsolete("Do not translate by using ToString. Use TranslatableString.Translate().", true)] + public static implicit operator string(TranslatableString _) => "NOT TRANSLATED CORRECTLY!"; +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index 279ca575..ef121584 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -1,5 +1,6 @@ @using LBPUnion.ProjectLighthouse.Helpers @using LBPUnion.ProjectLighthouse.Helpers.Extensions +@using LBPUnion.ProjectLighthouse.Localization @using LBPUnion.ProjectLighthouse.Types @using LBPUnion.ProjectLighthouse.Types.Settings @model LBPUnion.ProjectLighthouse.Pages.Layouts.BaseLayout @@ -7,21 +8,21 @@ @{ if (Model!.User == null) { - Model.NavigationItemsRight.Add(new PageNavigationItem("Login / Register", "/login", "sign in")); + Model.NavigationItemsRight.Add(new PageNavigationItem(new TranslatableString(TranslationAreas.BaseLayout, "Login / Register"), "/login", "sign in")); } else { if (ServerSettings.Instance.UseExternalAuth) { - Model.NavigationItems.Add(new PageNavigationItem("Authentication", "/authentication", "key")); + Model.NavigationItems.Add(new PageNavigationItem(new TranslatableString(TranslationAreas.BaseLayout, "Authentication"), "/authentication", "key")); } - Model.NavigationItemsRight.Add(new PageNavigationItem("Profile", "/user/" + Model.User.UserId, "user alternate")); + Model.NavigationItemsRight.Add(new PageNavigationItem(new TranslatableString(TranslationAreas.BaseLayout, "Profile"), "/user/" + Model.User.UserId, "user alternate")); @if (Model.User.IsAdmin) { - Model.NavigationItemsRight.Add(new PageNavigationItem("Admin Panel", "/admin", "cogs")); + Model.NavigationItemsRight.Add(new PageNavigationItem(new TranslatableString(TranslationAreas.BaseLayout, "Admin Panel"), "/admin", "cogs")); } - Model.NavigationItemsRight.Add(new PageNavigationItem("Log out", "/logout", "user alternate slash")); // should always be last + Model.NavigationItemsRight.Add(new PageNavigationItem(new TranslatableString(TranslationAreas.BaseLayout, "Log out"), "/logout", "user alternate slash")); // should always be last } Model.IsMobile = Model.Request.IsMobile(); @@ -93,7 +94,7 @@ @if (!Model.IsMobile) { - @navigationItem.Name + @Model.Translate(navigationItem.Name) } } @@ -108,7 +109,7 @@ @if (!Model.IsMobile) { - @navigationItem.Name + @Model.Translate(navigationItem.Name) } } diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs index 58056e32..e1b77818 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs @@ -1,22 +1,19 @@ #nullable enable +using System; using System.Collections.Generic; +using LBPUnion.ProjectLighthouse.Localization; +using LBPUnion.ProjectLighthouse.Localization.StringLists; using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc.RazorPages; namespace LBPUnion.ProjectLighthouse.Pages.Layouts; public class BaseLayout : PageModel { - public readonly Database Database; - public readonly List NavigationItems = new() - { - new PageNavigationItem("Home", "/", "home"), - new PageNavigationItem("Users", "/users/0", "user friends"), - new PageNavigationItem("Photos", "/photos/0", "camera"), - new PageNavigationItem("Levels", "/slots/0", "certificate"), - }; + public readonly List NavigationItems = new(); public readonly List NavigationItemsRight = new(); public string Description = string.Empty; @@ -31,6 +28,11 @@ public class BaseLayout : PageModel public BaseLayout(Database database) { this.Database = database; + + this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderHome, "/", "home")); + this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderUsers, "/users/0", "user friends")); + this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderPhotos, "/photos/0", "camera")); + this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderSlots, "/slots/0", "certificate")); } public new User? User { @@ -41,4 +43,20 @@ public class BaseLayout : PageModel } set => this.user = value; } + + public string Translate(TranslatableString translatableString) + { + string lang; + IRequestCultureFeature? requestCulture = Request.HttpContext.Features.Get(); + + if (requestCulture == null) lang = "en-UD"; // TODO: change to en-US when i can verify this is working + else + { + lang = requestCulture.RequestCulture.UICulture.Name; + } + + Console.WriteLine(lang); + + return translatableString.Translate(lang); + } } \ No newline at end of file diff --git a/ProjectLighthouse/Startup/Startup.cs b/ProjectLighthouse/Startup/Startup.cs index 97c6833f..488e6371 100644 --- a/ProjectLighthouse/Startup/Startup.cs +++ b/ProjectLighthouse/Startup/Startup.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using Kettu; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Localization; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; @@ -12,6 +16,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -85,6 +90,19 @@ public class Startup } ); + services.Configure + ( + config => + { + List languages = LocalizationManager.GetAvailableLanguages().Select(l => new CultureInfo(l)).ToList(); + + config.DefaultRequestCulture = new RequestCulture(new CultureInfo("en-US")); + + config.SupportedCultures = languages; + config.SupportedUICultures = languages; + } + ); + #if DEBUG services.AddSingleton(); #else @@ -123,6 +141,8 @@ public class Startup } ); + app.UseRequestLocalization(); + // Logs every request and the response to it // Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news" // Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr" diff --git a/ProjectLighthouse/Types/PageNavigationItem.cs b/ProjectLighthouse/Types/PageNavigationItem.cs index 11068c0a..c7ad4c5d 100644 --- a/ProjectLighthouse/Types/PageNavigationItem.cs +++ b/ProjectLighthouse/Types/PageNavigationItem.cs @@ -1,16 +1,18 @@ +using LBPUnion.ProjectLighthouse.Localization; + #nullable enable namespace LBPUnion.ProjectLighthouse.Types; public class PageNavigationItem { - public PageNavigationItem(string name, string url, string? icon = null) + public PageNavigationItem(TranslatableString name, string url, string? icon = null) { this.Name = name; this.Url = url; this.Icon = icon; } - public string Name { get; set; } + public TranslatableString Name { get; set; } public string Url { get; set; } public string? Icon { get; set; } } \ No newline at end of file