localize time zone names

This commit is contained in:
sbwalker
2025-07-23 14:52:18 -04:00
parent 190d973b77
commit 962488fd34
8 changed files with 204 additions and 7 deletions

View File

@ -53,6 +53,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ISyncService, SyncService>();
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
services.AddScoped<ICookieConsentService, CookieConsentService>();
services.AddScoped<ITimeZoneService, TimeZoneService>();
services.AddScoped<IOutputCacheService, OutputCacheService>();
// providers

View File

@ -9,6 +9,7 @@
@inject INotificationService NotificationService
@inject IFileService FileService
@inject IFolderService FolderService
@inject ITimeZoneService TimeZoneService
@inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@ -366,7 +367,6 @@
}
@code {
private List<Models.TimeZone> _timezones;
private bool _initialized = false;
private string _passwordrequirements;
private string _username = string.Empty;
@ -380,6 +380,7 @@
private string _displayname = string.Empty;
private FileManager _filemanager;
private int _folderid = -1;
private List<Models.TimeZone> _timezones;
private string _timezoneid = string.Empty;
private int _photofileid = -1;
private File _photo = null;
@ -403,7 +404,7 @@
_togglepassword = SharedLocalizer["ShowPassword"];
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
_timezones = Utilities.GetTimeZones();
_timezones = TimeZoneService.GetTimeZones();
if (PageState.User != null)
{

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Universal" xml:space="preserve">
<value>(UTC) Coordinated Universal Time</value>
</data>
<data name="US/Eastern" xml:space="preserve">
<value>(UTC-05:00) Eastern Time (US &amp; Canada)</value>
</data>
<data name="US/Pacific" xml:space="preserve">
<value>(UTC-08:00) Pacific Time (US &amp; Canada)</value>
</data>
</root>

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Services
{
/// <summary>
/// Service to retrieve <see cref="TimeZone"/> entries
/// </summary>
public interface ITimeZoneService
{
/// <summary>
/// Get the list of time zones
/// </summary>
/// <returns></returns>
List<TimeZone> GetTimeZones();
}
}

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Localization;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class TimeZoneService : ITimeZoneService
{
private readonly IStringLocalizer<TimeZoneResources> _TimeZoneLocalizer;
public TimeZoneService(IStringLocalizer<TimeZoneResources> TimeZoneLocalizer)
{
_TimeZoneLocalizer = TimeZoneLocalizer;
}
public List<TimeZone> GetTimeZones()
{
var _timezones = new List<TimeZone>();
foreach (var timezone in Utilities.GetTimeZones())
{
_timezones.Add(new TimeZone
{
Id = timezone.Id,
DisplayName = _TimeZoneLocalizer[timezone.Id]
});
}
return _timezones.OrderBy(item => item.DisplayName).ToList();
}
}
}

View File

@ -0,0 +1,14 @@
namespace Oqtane
{
/// <summary>
/// Dummy class used to collect shared resource strings for this application
/// </summary>
/// <remarks>
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
/// The class must reside at the project root.
/// </remarks>
public class TimeZoneResources
{
}
}

View File

@ -104,6 +104,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
services.AddScoped<IImageService, ImageService>();
services.AddScoped<ICookieConsentService, ServerCookieConsentService>();
services.AddScoped<ITimeZoneService, TimeZoneService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();

View File

@ -695,11 +695,11 @@ namespace Oqtane.Shared
public static List<TimeZone> GetTimeZones()
{
return [.. DateTimeZoneProviders.Tzdb.GetAllZones()
.Select(tz => new TimeZone()
{
Id = tz.Id,
DisplayName = tz.ToString()
})];
.Select(tz => new TimeZone()
{
Id = tz.Id,
DisplayName = tz.Id
})];
}
public static bool IsEffectiveAndNotExpired(DateTime? effectiveDate, DateTime? expiryDate)