Added separate localization & clean up (#5227)

* Added separate localization & clean up

* Added new methods docs

* Added GetFoundCultures method

* Clean up code

* Removed some formating shit

* Do better CultureInfo comparison

* Oops

* Review

* Command fixes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
This commit is contained in:
Tornado Tech
2025-04-03 00:41:02 +10:00
committed by GitHub
parent ab55d5b2f2
commit 4f96c2d233
6 changed files with 158 additions and 3 deletions

View File

@@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-cultureinfo = "{$arg}" is not valid CultureInfo.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-parse-failure-session = There is no session with username: {$username}
@@ -572,3 +573,8 @@ cmd-pvs-override-info-desc = Prints information about any PVS overrides associat
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
cmd-localization_set_culture-culture-name = <cultureName>
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands;
[UsedImplicitly]
internal sealed class LocalizationSetCulture : LocalizedCommands
{
private const string Name = "localization_set_culture";
private const int ArgumentCount = 1;
public override string Command => Name;
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != ArgumentCount)
{
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
return;
}
CultureInfo culture;
try
{
culture = CultureInfo.GetCultureInfo(args[0], predefinedOnly: false);
}
catch (CultureNotFoundException)
{
shell.WriteError(Loc.GetString("cmd-parse-failure-cultureinfo", ("arg", args[0])));
return;
}
LocalizationManager.SetCulture(culture);
shell.WriteLine(LocalizationManager.GetString("cmd-localization_set_culture-changed",
("code", culture.Name),
("nativeName", culture.NativeName),
("englishName", culture.EnglishName)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return args.Length switch
{
1 => CompletionResult.FromHintOptions(GetCultureNames(),
LocalizationManager.GetString("cmd-localization_set_culture-culture-name")),
_ => CompletionResult.Empty
};
}
private static HashSet<string> GetCultureNames()
{
var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(x => !string.IsNullOrEmpty(x.Name))
.ToArray();
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
allNames.UnionWith(cultureInfos.Select(x => x.TwoLetterISOLanguageName));
allNames.UnionWith(cultureInfos.Select(x => x.Name));
return allNames;
}
}

View File

@@ -1874,5 +1874,12 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<int> ToolshedNearbyEntitiesLimit =
CVarDef.Create("toolshed.nearby_entities_limit", 5, CVar.SERVER | CVar.REPLICATED);
/*
* Localization
*/
public static readonly CVarDef<string> LocCultureName =
CVarDef.Create("loc.culture_name", "en-US", CVar.ARCHIVE);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using JetBrains.Annotations;
@@ -95,9 +96,15 @@ namespace Robust.Shared.Localization
CultureInfo? DefaultCulture { get; set; }
/// <summary>
/// Checks if the culture has been loaded.
/// Checks if the culture is loaded, if not,
/// loads it via <see cref="ILocalizationManager.LoadCulture"/>
/// and then set it as <see cref="ILocalizationManager.DefaultCulture"/>.
/// </summary>
void SetCulture(CultureInfo culture);
/// <summary>
/// Checks to see if the culture has been loaded.
/// </summary>
/// <param name="culture"></param>
bool HasCulture(CultureInfo culture);
/// <summary>
@@ -106,6 +113,17 @@ namespace Robust.Shared.Localization
/// <param name="culture"></param>
void LoadCulture(CultureInfo culture);
/// <summary>
/// Loads <see cref="CultureInfo"/> obtained from <see cref="CVars.LocCultureName"/>,
/// they are different for client and server, and also can be saved.
/// </summary>
CultureInfo SetDefaultCulture();
/// <summary>
/// Returns all locale directories from the game's resources.
/// </summary>
List<CultureInfo> GetFoundCultures();
/// <summary>
/// Sets culture to be used in the absence of the main one.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -12,6 +13,7 @@ using Linguini.Shared.Types.Bundle;
using Linguini.Syntax.Ast;
using Linguini.Syntax.Parser;
using Linguini.Syntax.Parser.Error;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -24,6 +26,9 @@ namespace Robust.Shared.Localization
{
internal sealed partial class LocalizationManager : ILocalizationManagerInternal, IPostInjectInit
{
private static readonly ResPath LocaleDirPath = new("/Locale");
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
@@ -41,6 +46,18 @@ namespace Robust.Shared.Localization
_prototype.PrototypesReloaded += OnPrototypesReloaded;
}
public CultureInfo SetDefaultCulture()
{
var code = _configuration.GetCVar(CVars.LocCultureName);
var culture = CultureInfo.GetCultureInfo(code, predefinedOnly: false);
SetCulture(culture);
// Return the culture for further work with it,
// like of adding functions
return culture;
}
public string GetString(string messageId)
{
if (_defaultCulture == null)
@@ -337,6 +354,17 @@ namespace Robust.Shared.Localization
}
}
public void SetCulture(CultureInfo culture)
{
if (!HasCulture(culture))
LoadCulture(culture);
if (DefaultCulture?.NameEquals(culture) ?? false)
return;
DefaultCulture = culture;
}
public bool HasCulture(CultureInfo culture)
{
return _contexts.ContainsKey(culture);
@@ -344,6 +372,10 @@ namespace Robust.Shared.Localization
public void LoadCulture(CultureInfo culture)
{
// Attempting to load an already loaded culture
if (HasCulture(culture))
throw new InvalidOperationException("Culture is already loaded");
var bundle = LinguiniBuilder.Builder()
.CultureInfo(culture)
.SkipResources()
@@ -358,6 +390,20 @@ namespace Robust.Shared.Localization
DefaultCulture ??= culture;
}
public List<CultureInfo> GetFoundCultures()
{
var result = new List<CultureInfo>();
foreach (var name in _res.ContentGetDirectoryEntries(LocaleDirPath))
{
// Remove last "/" symbol
// Example "en-US/" -> "en-US"
var cultureName = name.TrimEnd('/');
result.Add(CultureInfo.GetCultureInfo(cultureName, predefinedOnly: false));
}
return result;
}
public void SetFallbackCluture(params CultureInfo[] cultures)
{
_fallbackCultures = Array.Empty<(CultureInfo, FluentBundle)>();
@@ -438,7 +484,7 @@ namespace Robust.Shared.Localization
// Load data from .ftl files.
// Data is loaded from /Locale/<language-code>/*
var root = new ResPath($"/Locale/{culture.Name}/");
var root = LocaleDirPath / culture.Name;
var files = resourceManager.ContentFindFiles(root)
.Where(c => c.Filename.EndsWith(".ftl", StringComparison.InvariantCultureIgnoreCase))

View File

@@ -0,0 +1,11 @@
using System.Globalization;
namespace Robust.Shared.Utility;
public static class CultureInfoExtension
{
public static bool NameEquals(this CultureInfo cultureInfo, CultureInfo otherCultureInfo)
{
return cultureInfo.Name == otherCultureInfo.Name;
}
}