Unstable 0.17.0.0
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class AddedPunctuationLString : LocalizedString
|
||||
{
|
||||
private readonly ImmutableArray<LocalizedString> nestedStrs;
|
||||
private readonly char punctuationSymbol;
|
||||
|
||||
public AddedPunctuationLString(char symbol, params LocalizedString[] nStrs) { nestedStrs = nStrs.ToImmutableArray(); punctuationSymbol = symbol; }
|
||||
|
||||
public override bool Loaded => nestedStrs.All(s => s.Loaded);
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
string separator = "";
|
||||
if (GameSettings.CurrentConfig.Language == "French".ToLanguageIdentifier())
|
||||
{
|
||||
bool addNonBreakingSpace =
|
||||
punctuationSymbol == ':' || punctuationSymbol == ';' ||
|
||||
punctuationSymbol == '!' || punctuationSymbol == '?';
|
||||
separator = addNonBreakingSpace ?
|
||||
new string(new char[] { (char)(0xA0), punctuationSymbol, ' ' }) :
|
||||
new string(new char[] { punctuationSymbol, ' ' });
|
||||
}
|
||||
else
|
||||
{
|
||||
separator = new string(new char[] { punctuationSymbol, ' ' });
|
||||
}
|
||||
cachedValue = string.Join(separator, nestedStrs.Select(str => str.Value));
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class CapitalizeLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString nestedStr;
|
||||
|
||||
public CapitalizeLString(LocalizedString nStr) { nestedStr = nStr; }
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
string str = nestedStr.Value;
|
||||
if (!string.IsNullOrEmpty(str))
|
||||
{
|
||||
cachedValue = char.ToUpper(str[0]) + str[1..];
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedValue = "";
|
||||
}
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ConcatLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString left;
|
||||
private readonly LocalizedString right;
|
||||
|
||||
public ConcatLString(LocalizedString l, LocalizedString r)
|
||||
{
|
||||
left = l; right = r;
|
||||
}
|
||||
|
||||
public override bool Loaded => left.Loaded || right.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = left.Value + right.Value;
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class FallbackLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString primary;
|
||||
private readonly LocalizedString fallback;
|
||||
|
||||
private bool primaryIsLoaded = false;
|
||||
|
||||
public FallbackLString(LocalizedString primary, LocalizedString fallback)
|
||||
{
|
||||
if (primary is FallbackLString {primary: { } innerPrimary, fallback: { } innerFallback})
|
||||
{
|
||||
this.primary = innerPrimary;
|
||||
this.fallback = innerFallback.Fallback(fallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MustRetrieveValue()
|
||||
{
|
||||
return base.MustRetrieveValue()
|
||||
|| MustRetrieveValue(primary)
|
||||
|| MustRetrieveValue(fallback)
|
||||
|| primaryIsLoaded != primary.Loaded;
|
||||
}
|
||||
|
||||
public override bool Loaded => primary.Loaded || fallback.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = primary.Value;
|
||||
primaryIsLoaded = primary.Loaded;
|
||||
if (!primary.Loaded)
|
||||
{
|
||||
cachedValue = fallback.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class FormattedLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString str;
|
||||
private readonly ImmutableArray<LocalizedString> subStrs;
|
||||
public FormattedLString(LocalizedString str, params LocalizedString[] subStrs)
|
||||
{
|
||||
this.str = str;
|
||||
this.subStrs = subStrs.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override bool Loaded => str.Loaded && subStrs.All(s => s.Loaded);
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
//TODO: possibly broken!
|
||||
cachedValue = string.Format(str.Value, subStrs.Select(s => s.Value as object).ToArray());
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class InputTypeLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString nestedStr;
|
||||
public InputTypeLString(LocalizedString nStr) { nestedStr = nStr; }
|
||||
|
||||
protected override bool MustRetrieveValue()
|
||||
{
|
||||
//TODO: check for config changes!
|
||||
return base.MustRetrieveValue();
|
||||
}
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = nestedStr.Value;
|
||||
#if CLIENT
|
||||
//TODO: server shouldn't have this type at all
|
||||
foreach (InputType? inputType in Enum.GetValues(typeof(InputType)))
|
||||
{
|
||||
if (!inputType.HasValue) { continue; }
|
||||
cachedValue = cachedValue.Replace($"[{inputType}]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType.Value).Value, StringComparison.OrdinalIgnoreCase);
|
||||
cachedValue = cachedValue.Replace($"[InputType.{inputType}]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType.Value).Value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
#endif
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class JoinLString : LocalizedString
|
||||
{
|
||||
private readonly IEnumerable<LocalizedString> subStrs;
|
||||
private readonly string separator;
|
||||
|
||||
public JoinLString(string separator, IEnumerable<LocalizedString> subStrs)
|
||||
{
|
||||
this.separator = separator; this.subStrs = subStrs;
|
||||
}
|
||||
|
||||
public override bool Loaded => subStrs.All(s => s.Loaded);
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = string.Join(separator, subStrs);
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class LocalizedString : IComparable
|
||||
{
|
||||
protected enum LoadedSuccessfully
|
||||
{
|
||||
Unknown,
|
||||
No,
|
||||
Yes
|
||||
}
|
||||
|
||||
protected LanguageIdentifier language { get; private set; } = LanguageIdentifier.None;
|
||||
private int languageVersion = 0;
|
||||
|
||||
protected string cachedValue = "";
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MustRetrieveValue()) { RetrieveValue(); }
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length => Value.Length;
|
||||
|
||||
public abstract bool Loaded { get; }
|
||||
|
||||
protected void UpdateLanguage()
|
||||
{
|
||||
language = GameSettings.CurrentConfig.Language;
|
||||
languageVersion = TextManager.LanguageVersion;
|
||||
}
|
||||
|
||||
protected virtual bool MustRetrieveValue() //this can't be called on other LocalizedStrings by derived classes
|
||||
{
|
||||
return language != GameSettings.CurrentConfig.Language || languageVersion != TextManager.LanguageVersion;
|
||||
}
|
||||
|
||||
protected static bool MustRetrieveValue(LocalizedString str) //this can be called by derived classes
|
||||
{
|
||||
return str.MustRetrieveValue();
|
||||
}
|
||||
|
||||
public abstract void RetrieveValue();
|
||||
|
||||
public static implicit operator LocalizedString(string value) => new RawLString(value);
|
||||
public static implicit operator LocalizedString(char value) => new RawLString(value.ToString());
|
||||
|
||||
public static LocalizedString operator+(LocalizedString left, LocalizedString right) => new ConcatLString(left, right);
|
||||
public static LocalizedString operator+(LocalizedString left, object right) => left + (right.ToString() ?? "");
|
||||
public static LocalizedString operator+(object left, LocalizedString right) => (left.ToString() ?? "") + right;
|
||||
|
||||
public static bool operator==(LocalizedString? left, LocalizedString? right)
|
||||
{
|
||||
return left?.Value == right?.Value;
|
||||
}
|
||||
|
||||
public static bool operator!=(LocalizedString? left, LocalizedString? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public bool Contains(string subStr, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return !Value.IsNullOrEmpty() && Value.Contains(subStr, comparison);
|
||||
}
|
||||
|
||||
public bool Contains(char chr, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return Value.Contains(chr, comparison);
|
||||
}
|
||||
|
||||
public virtual LocalizedString ToUpper()
|
||||
{
|
||||
return new UpperLString(this);
|
||||
}
|
||||
|
||||
public static LocalizedString Join(string separator, params LocalizedString[] subStrs)
|
||||
{
|
||||
return Join(separator, (IEnumerable<LocalizedString>)subStrs);
|
||||
}
|
||||
|
||||
public static LocalizedString Join(string separator, IEnumerable<LocalizedString> subStrs)
|
||||
{
|
||||
return new JoinLString(separator, subStrs);
|
||||
}
|
||||
|
||||
public LocalizedString Fallback(LocalizedString fallback)
|
||||
{
|
||||
return new FallbackLString(this, fallback);
|
||||
}
|
||||
|
||||
public IReadOnlyList<LocalizedString> Split(params char[] separators)
|
||||
{
|
||||
var splitter = new LStringSplitter(this, separators);
|
||||
return splitter.Substrings;
|
||||
}
|
||||
|
||||
public LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
return new ReplaceLString(this, stringComparison, (find, replace));
|
||||
}
|
||||
|
||||
public LocalizedString Replace(string find, LocalizedString replace, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
return new ReplaceLString(this, stringComparison, (find.ToIdentifier(), replace));
|
||||
}
|
||||
|
||||
public LocalizedString Replace(LocalizedString find, LocalizedString replace,
|
||||
StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
return new ReplaceLString(this, stringComparison, (find, replace));
|
||||
}
|
||||
|
||||
public LocalizedString TrimStart()
|
||||
{
|
||||
return new TrimLString(this, TrimLString.Mode.Start);
|
||||
}
|
||||
|
||||
public LocalizedString TrimEnd()
|
||||
{
|
||||
return new TrimLString(this, TrimLString.Mode.End);
|
||||
}
|
||||
|
||||
public LocalizedString ToLower()
|
||||
{
|
||||
return new LowerLString(this);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is LocalizedString lStr) { return Equals(lStr, StringComparison.Ordinal); }
|
||||
if (obj is string str) { return Equals(str, StringComparison.Ordinal); }
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public bool Equals(LocalizedString other, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return Equals(other.Value, comparison);
|
||||
}
|
||||
|
||||
public bool Equals(string other, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return string.Equals(Value, other, comparison);
|
||||
}
|
||||
|
||||
public bool StartsWith(LocalizedString other, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return StartsWith(other.Value, comparison);
|
||||
}
|
||||
|
||||
public bool StartsWith(string other, StringComparison comparison = StringComparison.Ordinal)
|
||||
{
|
||||
return Value.StartsWith(other, comparison);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
return Value.CompareTo(obj?.ToString() ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LowerLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString nestedStr;
|
||||
|
||||
public LowerLString(LocalizedString nestedStr)
|
||||
{
|
||||
this.nestedStr = nestedStr;
|
||||
}
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = nestedStr.Value.ToLowerInvariant();
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class RawLString : LocalizedString
|
||||
{
|
||||
public RawLString(string value) { cachedValue = value; }
|
||||
|
||||
protected override bool MustRetrieveValue() => false;
|
||||
|
||||
public override bool Loaded => true;
|
||||
public override void RetrieveValue() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ReplaceLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString nestedStr;
|
||||
private readonly ImmutableDictionary<LocalizedString, (LocalizedString Value, FormatCapitals FormatCapitals)> replacements;
|
||||
private readonly StringComparison stringComparison;
|
||||
|
||||
public ReplaceLString(LocalizedString nStr, StringComparison sc, IEnumerable<(LocalizedString Key, LocalizedString Value, FormatCapitals FormatCapitals)> r)
|
||||
{
|
||||
nestedStr = nStr;
|
||||
replacements = r.Select(kvf => (kvf.Key, (kvf.Value, kvf.FormatCapitals))).ToImmutableDictionary();
|
||||
stringComparison = sc;
|
||||
}
|
||||
|
||||
public ReplaceLString(LocalizedString nStr, StringComparison sc, params (LocalizedString Key, LocalizedString Value)[] r)
|
||||
: this(nStr, sc, r.Select(kv => (kv.Key, kv.Value, FormatCapitals.No))) { }
|
||||
|
||||
public ReplaceLString(LocalizedString nStr, StringComparison sc, IEnumerable<(Identifier Key, LocalizedString Value, FormatCapitals FormatCapitals)> r)
|
||||
: this(nStr, sc, r.Select(p => ((LocalizedString)p.Key.Value, p.Value, p.FormatCapitals))) { }
|
||||
|
||||
public ReplaceLString(LocalizedString nStr, StringComparison sc, params (Identifier Key, LocalizedString Value)[] r)
|
||||
: this(nStr, sc, r.Select(kv => ((LocalizedString)kv.Key.Value, kv.Value, FormatCapitals.No))) { }
|
||||
|
||||
private static string HandleVariableCapitalization(string text, string variableTag, string variableValue)
|
||||
{
|
||||
int index = text.IndexOf(variableTag, StringComparison.InvariantCulture) - 1;
|
||||
if (index == -1)
|
||||
{
|
||||
return variableValue;
|
||||
}
|
||||
|
||||
for (int i = index; i >= 0; i--)
|
||||
{
|
||||
if (char.IsWhiteSpace(text[i])) { continue; }
|
||||
|
||||
if (text[i] != '.')
|
||||
{
|
||||
variableValue = variableValue.ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
variableValue = TextManager.Capitalize(variableValue).Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return variableValue;
|
||||
}
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = nestedStr.Value;
|
||||
foreach (var varName in replacements.Keys)
|
||||
{
|
||||
string key = varName.Value;
|
||||
string value = replacements[varName].Value.Value;
|
||||
if (replacements[varName].FormatCapitals == FormatCapitals.Yes)
|
||||
{
|
||||
value = HandleVariableCapitalization(cachedValue, key, value);
|
||||
}
|
||||
cachedValue = cachedValue.Replace(key, value, stringComparison);
|
||||
}
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ServerMsgLString : LocalizedString
|
||||
{
|
||||
private static readonly Regex reFormattedMessage =
|
||||
new Regex(@"^(?<variable>[\[\].a-z0-9_]+?)=(?<formatter>[a-z0-9_]+?)\((?<value>.+?)\)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex reReplacedMessage = new Regex(@"^(?<variable>[\[\].a-z0-9_]+?)=(?<message>.*)$",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly ImmutableDictionary<Identifier, Func<string, string?>> messageFormatters =
|
||||
new Dictionary<Identifier, Func<string, string?>>
|
||||
{
|
||||
{
|
||||
"duration".ToIdentifier(),
|
||||
secondsValue => double.TryParse(secondsValue, out var seconds)
|
||||
? $"{TimeSpan.FromSeconds(seconds):g}"
|
||||
: null
|
||||
}
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private static readonly ImmutableHashSet<char> serverMessageCharacters =
|
||||
new [] {'~', '[', ']', '='}.ToImmutableHashSet();
|
||||
|
||||
private readonly string serverMessage;
|
||||
private readonly ImmutableArray<string> messageSplit;
|
||||
|
||||
public ServerMsgLString(string serverMsg)
|
||||
{
|
||||
serverMessage = serverMsg;
|
||||
messageSplit = serverMessage.Split("/").ToImmutableArray();
|
||||
}
|
||||
|
||||
private static bool IsServerMessageWithVariables(string message) =>
|
||||
serverMessageCharacters.All(message.Contains);
|
||||
|
||||
private LoadedSuccessfully loadedSuccessfully = LoadedSuccessfully.Unknown;
|
||||
public override bool Loaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (loadedSuccessfully == LoadedSuccessfully.Unknown) { RetrieveValue(); }
|
||||
return loadedSuccessfully == LoadedSuccessfully.Yes;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
Dictionary<string, string> replacedMessages = new Dictionary<string, string>();
|
||||
bool translationsFound = false;
|
||||
|
||||
string? TranslateMessage(string input)
|
||||
{
|
||||
string? message = input;
|
||||
if (message.EndsWith("~", StringComparison.Ordinal))
|
||||
{
|
||||
message = message.Substring(0, message.Length - 1);
|
||||
}
|
||||
|
||||
if (!IsServerMessageWithVariables(message) && !message.Contains('=')) // No variables, try to translate
|
||||
{
|
||||
foreach (var replacedMessage in replacedMessages)
|
||||
{
|
||||
message = message.Replace(replacedMessage.Key, replacedMessage.Value);
|
||||
}
|
||||
|
||||
if (message.Contains(" "))
|
||||
{
|
||||
return message;
|
||||
} // Spaces found, do not translate
|
||||
|
||||
var msg = TextManager.Get(message);
|
||||
if (msg.Loaded) // If a translation was found, otherwise use the original
|
||||
{
|
||||
message = msg.Value;
|
||||
translationsFound = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string? messageVariable = null;
|
||||
var matchFormatted = reFormattedMessage.Match(message);
|
||||
if (matchFormatted.Success)
|
||||
{
|
||||
var formatter = matchFormatted.Groups["formatter"].ToString().ToIdentifier();
|
||||
if (messageFormatters.TryGetValue(formatter, out var formatterFn))
|
||||
{
|
||||
var formattedValue = formatterFn(matchFormatted.Groups["value"].ToString());
|
||||
if (formattedValue != null)
|
||||
{
|
||||
messageVariable = matchFormatted.Groups["variable"].ToString();
|
||||
message = formattedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messageVariable == null)
|
||||
{
|
||||
var matchReplaced = reReplacedMessage.Match(message);
|
||||
if (matchReplaced.Success)
|
||||
{
|
||||
messageVariable = matchReplaced.Groups["variable"].ToString();
|
||||
message = matchReplaced.Groups["message"].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var replacedMessage in replacedMessages)
|
||||
{
|
||||
message = message.Replace(replacedMessage.Key, replacedMessage.Value);
|
||||
}
|
||||
|
||||
string[] messageWithVariables = message.Split('~');
|
||||
|
||||
var msg = TextManager.Get(messageWithVariables[0]);
|
||||
|
||||
if (msg.Loaded) // If a translation was found, otherwise use the original
|
||||
{
|
||||
message = msg.Value;
|
||||
translationsFound = true;
|
||||
}
|
||||
else if (messageVariable == null)
|
||||
{
|
||||
return message; // No translation found, probably caused by player input -> skip variable handling
|
||||
}
|
||||
|
||||
// First index is always the message identifier -> start at 1
|
||||
for (int j = 1; j < messageWithVariables.Length; j++)
|
||||
{
|
||||
string[] variableAndValue = messageWithVariables[j].Split('=');
|
||||
message = message.Replace(variableAndValue[0],
|
||||
variableAndValue[1].Length > 1 && variableAndValue[1][0] == '§'
|
||||
? TextManager.Get(variableAndValue[1].Substring(1)).Value
|
||||
: variableAndValue[1]);
|
||||
}
|
||||
|
||||
if (messageVariable != null)
|
||||
{
|
||||
replacedMessages[messageVariable] = message;
|
||||
message = null;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string translatedServerMessage = "";
|
||||
|
||||
for (int i = 0; i < messageSplit.Length; i++)
|
||||
{
|
||||
string? message = TranslateMessage(messageSplit[i]);
|
||||
if (message != null)
|
||||
{
|
||||
translatedServerMessage += message;
|
||||
}
|
||||
}
|
||||
|
||||
cachedValue = translationsFound ? translatedServerMessage : serverMessage;
|
||||
loadedSuccessfully = LoadedSuccessfully.Yes;
|
||||
}
|
||||
catch (IndexOutOfRangeException exception)
|
||||
{
|
||||
string errorMsg = $"Failed to translate server message \"{serverMessage}\".";
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg, exception);
|
||||
#endif
|
||||
GameAnalyticsManager.AddErrorEventOnce($"TextManager.GetServerMessage:{serverMessage}",
|
||||
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
cachedValue = errorMsg;
|
||||
loadedSuccessfully = LoadedSuccessfully.No;
|
||||
}
|
||||
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LStringSplitter
|
||||
{
|
||||
public IReadOnlyList<LocalizedString> Substrings => substrings;
|
||||
|
||||
private class SubstringList : IReadOnlyList<LocalizedString>
|
||||
{
|
||||
public SubstringList(LStringSplitter splitter) { this.splitter = splitter; }
|
||||
|
||||
private LStringSplitter splitter;
|
||||
private readonly List<LocalizedString> underlyingList = new List<LocalizedString>();
|
||||
|
||||
public List<LocalizedString> UnderlyingList
|
||||
{
|
||||
get
|
||||
{
|
||||
splitter.UpdateSubstrings();
|
||||
return underlyingList;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<LocalizedString> GetEnumerator() => UnderlyingList.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public int Count => UnderlyingList.Count;
|
||||
|
||||
public LocalizedString this[int index] => UnderlyingList[index];
|
||||
}
|
||||
|
||||
private readonly SubstringList substrings;
|
||||
private readonly char[] separators;
|
||||
private readonly LocalizedString originalString;
|
||||
private string[] substrValues;
|
||||
|
||||
private string cachedOriginal;
|
||||
|
||||
public bool Loaded => originalString.Loaded;
|
||||
|
||||
public LStringSplitter(LocalizedString input, params char[] separators)
|
||||
{
|
||||
originalString = input;
|
||||
substrings = new SubstringList(this);
|
||||
substrValues = Array.Empty<string>();
|
||||
this.separators = separators;
|
||||
cachedOriginal = "";
|
||||
}
|
||||
|
||||
private void UpdateSubstrings()
|
||||
{
|
||||
if (originalString.Value != cachedOriginal)
|
||||
{
|
||||
cachedOriginal = originalString.Value;
|
||||
substrValues = cachedOriginal.Split(separators);
|
||||
substrings.UnderlyingList.Clear();
|
||||
substrings.UnderlyingList.AddRange(Enumerable.Range(0, substrValues.Length).Select(i => new SplitLString(this, i) as LocalizedString));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetValue(int index)
|
||||
{
|
||||
UpdateSubstrings();
|
||||
return substrValues[index];
|
||||
}
|
||||
}
|
||||
|
||||
public class SplitLString : LocalizedString
|
||||
{
|
||||
private bool loaded = false;
|
||||
private readonly LStringSplitter splitter;
|
||||
private readonly int index;
|
||||
|
||||
public SplitLString(LStringSplitter splitter, int index)
|
||||
{
|
||||
this.splitter = splitter; this.index = index;
|
||||
}
|
||||
|
||||
public override bool Loaded => loaded && splitter.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
loaded = true;
|
||||
cachedValue = splitter.GetValue(index);
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class TagLString : LocalizedString
|
||||
{
|
||||
private readonly ImmutableArray<Identifier> tags;
|
||||
|
||||
public TagLString(params Identifier[] tags)
|
||||
{
|
||||
this.tags = tags.ToImmutableArray();
|
||||
}
|
||||
|
||||
private LoadedSuccessfully loadedSuccessfully = LoadedSuccessfully.Unknown;
|
||||
|
||||
public override bool Loaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (loadedSuccessfully == LoadedSuccessfully.Unknown) { RetrieveValue(); }
|
||||
return loadedSuccessfully == LoadedSuccessfully.Yes;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
UpdateLanguage();
|
||||
|
||||
(string value, bool loaded) tryLoad(LanguageIdentifier lang)
|
||||
{
|
||||
IReadOnlyList<string> candidates = Array.Empty<string>();
|
||||
int tagIndex = 0;
|
||||
|
||||
if (TextManager.TextPacks.TryGetValue(lang, out var packs))
|
||||
{
|
||||
while (candidates.Count == 0 && tagIndex < tags.Length)
|
||||
{
|
||||
foreach (var pack in packs)
|
||||
{
|
||||
if (pack.Texts.TryGetValue(tags[tagIndex], out var texts))
|
||||
{
|
||||
candidates = candidates.ListConcat(texts);
|
||||
}
|
||||
}
|
||||
tagIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
bool loaded = candidates.Count > 0;
|
||||
return (loaded ? candidates.GetRandomUnsynced() : "", loaded);
|
||||
}
|
||||
|
||||
var (value, loaded) = tryLoad(language);
|
||||
loadedSuccessfully = loaded ? LoadedSuccessfully.Yes : LoadedSuccessfully.No;
|
||||
cachedValue = value;
|
||||
if (!loaded && language != TextManager.DefaultLanguage)
|
||||
{
|
||||
(value, _) = tryLoad(TextManager.DefaultLanguage);
|
||||
cachedValue = value;
|
||||
//Notice how we don't set loadedSuccessfully again here.
|
||||
//This is by design; falling back to English means that
|
||||
//this text did NOT load successfully, so Loaded must
|
||||
//return false.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class TrimLString : LocalizedString
|
||||
{
|
||||
[Flags]
|
||||
public enum Mode { Start = 0x1, End = 0x2, Both=0x3 }
|
||||
private readonly LocalizedString nestedStr;
|
||||
private readonly Mode mode;
|
||||
|
||||
public TrimLString(LocalizedString nestedStr, Mode mode)
|
||||
{
|
||||
this.nestedStr = nestedStr;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = nestedStr.Value;
|
||||
if (mode.HasFlag(Mode.Start)) { cachedValue = cachedValue.TrimStart(); }
|
||||
if (mode.HasFlag(Mode.End)) { cachedValue = cachedValue.TrimEnd(); }
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class UpperLString : LocalizedString
|
||||
{
|
||||
private readonly LocalizedString nestedStr;
|
||||
|
||||
public UpperLString(LocalizedString nestedStr)
|
||||
{
|
||||
this.nestedStr = nestedStr;
|
||||
}
|
||||
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = nestedStr.Value.ToUpper();
|
||||
UpdateLanguage();
|
||||
}
|
||||
|
||||
public override LocalizedString ToUpper()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class RichString
|
||||
{
|
||||
protected bool loaded = false;
|
||||
protected LanguageIdentifier language = LanguageIdentifier.None;
|
||||
|
||||
protected string cachedSanitizedValue = "";
|
||||
public string SanitizedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MustRetrieveValue()) { RetrieveValue(); }
|
||||
return cachedSanitizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length => SanitizedValue.Length;
|
||||
|
||||
private readonly Func<string, string>? postProcess;
|
||||
private readonly bool shouldParseRichTextData;
|
||||
|
||||
private readonly LocalizedString originalStr;
|
||||
public LocalizedString NestedStr { get; private set; }
|
||||
public readonly LocalizedString SanitizedString;
|
||||
|
||||
#if CLIENT
|
||||
private readonly GUIFont? font;
|
||||
private readonly GUIComponentStyle? componentStyle;
|
||||
private bool forceUpperCase = false;
|
||||
|
||||
private bool fontOrStyleForceUpperCase
|
||||
=> font is { ForceUpperCase: true } || componentStyle is { ForceUpperCase: true };
|
||||
#endif
|
||||
|
||||
public ImmutableArray<RichTextData>? RichTextData { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
private RichString(
|
||||
LocalizedString nestedStr, bool shouldParseRichTextData, Func<string, string>? postProcess = null,
|
||||
GUIFont? font = null, GUIComponentStyle? componentStyle = null) : this(nestedStr, shouldParseRichTextData, postProcess)
|
||||
{
|
||||
this.font = font;
|
||||
this.componentStyle = componentStyle;
|
||||
}
|
||||
#endif
|
||||
|
||||
private RichString(LocalizedString nestedStr, bool shouldParseRichTextData, Func<string,string>? postProcess = null)
|
||||
{
|
||||
originalStr = nestedStr;
|
||||
NestedStr = originalStr;
|
||||
this.shouldParseRichTextData = shouldParseRichTextData;
|
||||
this.postProcess = postProcess;
|
||||
SanitizedString = new StripRichTagsLString(this);
|
||||
#if CLIENT
|
||||
this.font = null;
|
||||
this.componentStyle = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static RichString Rich(LocalizedString str, Func<string, string>? postProcess = null)
|
||||
{
|
||||
return new RichString(str, true, postProcess);
|
||||
}
|
||||
|
||||
public static RichString Plain(LocalizedString str)
|
||||
{
|
||||
return new RichString(str, false, postProcess: null);
|
||||
}
|
||||
|
||||
public static implicit operator LocalizedString(RichString richStr) => richStr.NestedStr;
|
||||
|
||||
public static implicit operator RichString(LocalizedString lStr)
|
||||
{
|
||||
#if DEBUG
|
||||
if (!lStr.IsNullOrEmpty() && lStr.Contains("‖"))
|
||||
{
|
||||
if (Debugger.IsAttached) { Debugger.Break(); }
|
||||
}
|
||||
#endif
|
||||
return Plain(lStr);
|
||||
}
|
||||
public static implicit operator RichString(string str) => (LocalizedString)str;
|
||||
|
||||
protected virtual bool MustRetrieveValue()
|
||||
{
|
||||
return NestedStr.Loaded != loaded
|
||||
|| language != GameSettings.CurrentConfig.Language
|
||||
#if CLIENT
|
||||
|| (fontOrStyleForceUpperCase != forceUpperCase)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
public void RetrieveValue()
|
||||
{
|
||||
#if CLIENT
|
||||
NestedStr = fontOrStyleForceUpperCase ? originalStr.ToUpper() : originalStr;
|
||||
#endif
|
||||
|
||||
if (shouldParseRichTextData)
|
||||
{
|
||||
RichTextData = Barotrauma.RichTextData.GetRichTextData(NestedStr.Value, out cachedSanitizedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedSanitizedValue = NestedStr.Value;
|
||||
}
|
||||
if (postProcess != null) { cachedSanitizedValue = postProcess(cachedSanitizedValue); }
|
||||
language = GameSettings.CurrentConfig.Language;
|
||||
loaded = NestedStr.Loaded;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public RichString CaseTiedToFontAndStyle(GUIFont? font, GUIComponentStyle? componentStyle)
|
||||
{
|
||||
return new RichString(originalStr, shouldParseRichTextData, postProcess, font, componentStyle);
|
||||
}
|
||||
#endif
|
||||
|
||||
public RichString ToUpper()
|
||||
{
|
||||
return new RichString(NestedStr.ToUpper(), shouldParseRichTextData, postProcess);
|
||||
}
|
||||
|
||||
public RichString ToLower()
|
||||
{
|
||||
return new RichString(NestedStr.ToLower(), shouldParseRichTextData, postProcess);
|
||||
}
|
||||
|
||||
public RichString Replace(string from, string to, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
{
|
||||
return new RichString(NestedStr.Replace(from, to, stringComparison), shouldParseRichTextData, postProcess);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return SanitizedValue;
|
||||
}
|
||||
|
||||
public bool Contains(string str, StringComparison stringComparison = StringComparison.Ordinal) =>
|
||||
SanitizedValue.Contains(str, stringComparison);
|
||||
|
||||
public bool Contains(char chr, StringComparison stringComparison = StringComparison.Ordinal) =>
|
||||
SanitizedValue.Contains(chr, stringComparison);
|
||||
|
||||
|
||||
public static bool operator ==(RichString? a, RichString? b)
|
||||
=> a?.SanitizedValue == b?.SanitizedValue
|
||||
#if CLIENT
|
||||
&& a?.font == b?.font
|
||||
&& a?.componentStyle == b?.componentStyle
|
||||
#endif
|
||||
;
|
||||
|
||||
public static bool operator !=(RichString? a, RichString? b) => !(a == b);
|
||||
|
||||
public static bool operator ==(RichString? a, LocalizedString? b)
|
||||
=> a?.SanitizedValue == b?.Value;
|
||||
|
||||
public static bool operator !=(RichString? a, LocalizedString? b) => !(a == b);
|
||||
|
||||
public static bool operator ==(LocalizedString? a, RichString? b)
|
||||
=> a?.Value == b?.SanitizedValue;
|
||||
|
||||
public static bool operator !=(LocalizedString? a, RichString? b) => !(a == b);
|
||||
|
||||
public static bool operator ==(RichString? a, string? b)
|
||||
=> a?.SanitizedValue == b;
|
||||
|
||||
public static bool operator !=(RichString? a, string? b) => !(a == b);
|
||||
|
||||
public static bool operator ==(string? a, RichString? b)
|
||||
=> a == b?.SanitizedValue;
|
||||
|
||||
public static bool operator !=(string? a, RichString? b) => !(a == b);
|
||||
}
|
||||
|
||||
public class StripRichTagsLString : LocalizedString
|
||||
{
|
||||
public readonly RichString RichStr;
|
||||
|
||||
public StripRichTagsLString(RichString richStr)
|
||||
{
|
||||
RichStr = richStr;
|
||||
}
|
||||
|
||||
public override bool Loaded => RichStr.NestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = RichStr.SanitizedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum FormatCapitals
|
||||
{
|
||||
Yes, No
|
||||
}
|
||||
|
||||
public static class TextManager
|
||||
{
|
||||
public readonly static LanguageIdentifier DefaultLanguage = "English".ToLanguageIdentifier();
|
||||
public readonly static ConcurrentDictionary<LanguageIdentifier, ImmutableHashSet<TextPack>> TextPacks = new ConcurrentDictionary<LanguageIdentifier, ImmutableHashSet<TextPack>>();
|
||||
public static IEnumerable<LanguageIdentifier> AvailableLanguages => TextPacks.Keys;
|
||||
|
||||
private readonly static Dictionary<Identifier, WeakReference<TagLString>> cachedStrings =
|
||||
new Dictionary<Identifier, WeakReference<TagLString>>();
|
||||
private static ImmutableHashSet<Identifier> nonCacheableTags =
|
||||
ImmutableHashSet<Identifier>.Empty;
|
||||
|
||||
public static int LanguageVersion { get; private set; } = 0;
|
||||
|
||||
private readonly static Regex isCJK = new Regex(
|
||||
@"\p{IsHangulJamo}|" +
|
||||
@"\p{IsHiragana}|" +
|
||||
@"\p{IsKatakana}|" +
|
||||
@"\p{IsCJKRadicalsSupplement}|" +
|
||||
@"\p{IsCJKSymbolsandPunctuation}|" +
|
||||
@"\p{IsEnclosedCJKLettersandMonths}|" +
|
||||
@"\p{IsCJKCompatibility}|" +
|
||||
@"\p{IsCJKUnifiedIdeographsExtensionA}|" +
|
||||
@"\p{IsCJKUnifiedIdeographs}|" +
|
||||
@"\p{IsHangulSyllables}|" +
|
||||
@"\p{IsCJKCompatibilityForms}");
|
||||
|
||||
/// <summary>
|
||||
/// Does the string contain symbols from Chinese, Japanese or Korean languages
|
||||
/// </summary>
|
||||
public static bool IsCJK(LocalizedString text)
|
||||
{
|
||||
return IsCJK(text.Value);
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) { return false; }
|
||||
return isCJK.IsMatch(text);
|
||||
}
|
||||
|
||||
public static bool ContainsTag(string tag)
|
||||
{
|
||||
return ContainsTag(tag.ToIdentifier());
|
||||
}
|
||||
|
||||
public static bool ContainsTag(Identifier tag)
|
||||
{
|
||||
return TextPacks[GameSettings.CurrentConfig.Language].Any(p => p.Texts.ContainsKey(tag));
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetAll(string tag)
|
||||
=> GetAll(tag.ToIdentifier());
|
||||
|
||||
public static IEnumerable<string> GetAll(Identifier tag)
|
||||
{
|
||||
return TextPacks[GameSettings.CurrentConfig.Language]
|
||||
.SelectMany(p => p.Texts.TryGetValue(tag, out var value)
|
||||
? (IEnumerable<string>)value
|
||||
: Array.Empty<string>());
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<Identifier, string>> GetAllTagTextPairs()
|
||||
{
|
||||
return TextPacks[GameSettings.CurrentConfig.Language]
|
||||
.SelectMany(p => p.Texts)
|
||||
.SelectMany(kvp => kvp.Value.Select(v => new KeyValuePair<Identifier, string>(kvp.Key, v)));
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetTextFiles()
|
||||
{
|
||||
return GetTextFilesRecursive(Path.Combine("Content", "Texts"));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetTextFilesRecursive(string directory)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(directory))
|
||||
{
|
||||
yield return file.CleanUpPath();
|
||||
}
|
||||
foreach (string subDir in Directory.GetDirectories(directory))
|
||||
{
|
||||
foreach (string file in GetTextFilesRecursive(subDir))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTranslatedLanguageName(LanguageIdentifier languageIdentifier)
|
||||
{
|
||||
return TextPacks[languageIdentifier].First().TranslatedName;
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock (cachedStrings)
|
||||
{
|
||||
cachedStrings.Clear();
|
||||
nonCacheableTags.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalizedString Get(params Identifier[] tags)
|
||||
{
|
||||
TagLString? str = null;
|
||||
lock (cachedStrings)
|
||||
{
|
||||
if (tags.Length == 1 && !nonCacheableTags.Contains(tags[0]))
|
||||
{
|
||||
var tag = tags[0];
|
||||
if (cachedStrings.TryGetValue(tag, out var strRef))
|
||||
{
|
||||
if (!strRef.TryGetTarget(out str))
|
||||
{
|
||||
cachedStrings.Remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (str is null && TextPacks.ContainsKey(GameSettings.CurrentConfig.Language))
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var pack in TextPacks[GameSettings.CurrentConfig.Language])
|
||||
{
|
||||
if (pack.Texts.TryGetValue(tag, out var texts))
|
||||
{
|
||||
count += texts.Length;
|
||||
if (count > 1) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
nonCacheableTags = nonCacheableTags.Add(tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = new TagLString(tags);
|
||||
cachedStrings.Add(tag, new WeakReference<TagLString>(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return str ?? new TagLString(tags);
|
||||
}
|
||||
|
||||
public static LocalizedString Get(params string[] tags)
|
||||
=> Get(tags.ToIdentifiers());
|
||||
|
||||
public static LocalizedString AddPunctuation(char punctuationSymbol, params LocalizedString[] texts)
|
||||
{
|
||||
return new AddedPunctuationLString(punctuationSymbol, texts);
|
||||
}
|
||||
|
||||
public static LocalizedString GetFormatted(Identifier tag, params object[] args)
|
||||
{
|
||||
return GetFormatted(new TagLString(tag), args);
|
||||
}
|
||||
|
||||
public static LocalizedString GetFormatted(LocalizedString str, params object[] args)
|
||||
{
|
||||
LocalizedString[] argStrs = new LocalizedString[args.Length];
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] is LocalizedString ls) { argStrs[i] = ls; }
|
||||
else { argStrs[i] = new RawLString(args[i].ToString() ?? ""); }
|
||||
}
|
||||
return new FormattedLString(str, argStrs);
|
||||
}
|
||||
|
||||
public static string FormatServerMessage(string str) => $"{str}~";
|
||||
|
||||
public static string FormatServerMessage(string message, params (string Key, string Value)[] keysWithValues)
|
||||
{
|
||||
if (keysWithValues.Length == 0)
|
||||
{
|
||||
return FormatServerMessage(message);
|
||||
}
|
||||
var startIndex = message.LastIndexOf('/') + 1;
|
||||
var endIndex = message.IndexOf('~', startIndex);
|
||||
if (endIndex == -1)
|
||||
{
|
||||
endIndex = message.Length - 1;
|
||||
}
|
||||
var textId = message.Substring(startIndex, endIndex - startIndex + 1);
|
||||
var prefixEntries = keysWithValues.Select((kv, index) =>
|
||||
{
|
||||
if (kv.Value.IndexOfAny(new char[] { '~', '/' }) != -1)
|
||||
{
|
||||
var kvStartIndex = kv.Value.LastIndexOf('/') + 1;
|
||||
return kv.Value.Substring(0, kvStartIndex) + $"[{textId}.{index}]={kv.Value.Substring(kvStartIndex)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}).Where(e => e != null).ToArray();
|
||||
return string.Join("",
|
||||
(prefixEntries.Length > 0 ? string.Join("/", prefixEntries) + "/" : ""),
|
||||
message,
|
||||
string.Join("", keysWithValues.Select((kv, index) => kv.Value.IndexOfAny(new char[] { '~', '/' }) != -1 ? $"~{kv.Key}=[{textId}.{index}]" : $"~{kv.Key}={kv.Value}"))
|
||||
);
|
||||
}
|
||||
|
||||
internal static string FormatServerMessageWithPronouns(CharacterInfo charInfo, string message, params (string Key, string Value)[] keysWithValues)
|
||||
{
|
||||
var pronounCategory = charInfo.Prefab.Pronouns;
|
||||
(string Key, string Value)[] pronounKwv = new[]
|
||||
{
|
||||
("[PronounLowercase]", charInfo.ReplaceVars($"Pronoun[{pronounCategory}]Lowercase")),
|
||||
("[PronounUppercase]", charInfo.ReplaceVars($"Pronoun[{pronounCategory}]")),
|
||||
("[PronounPossessiveLowercase]", charInfo.ReplaceVars($"PronounPossessive[{pronounCategory}]Lowercase")),
|
||||
("[PronounPossessiveUppercase]", charInfo.ReplaceVars($"PronounPossessive[{pronounCategory}]")),
|
||||
("[PronounReflexiveLowercase]", charInfo.ReplaceVars($"PronounReflexive[{pronounCategory}]Lowercase")),
|
||||
("[PronounReflexiveUppercase]", charInfo.ReplaceVars($"PronounReflexive[{pronounCategory}]"))
|
||||
};
|
||||
return FormatServerMessage(message, keysWithValues.Concat(pronounKwv).ToArray());
|
||||
}
|
||||
|
||||
// Same as string.Join(separator, parts) but performs the operation taking into account server message string replacements.
|
||||
public static string JoinServerMessages(string separator, string[] parts, string namePrefix = "part.")
|
||||
{
|
||||
|
||||
return string.Join("/",
|
||||
string.Join("/", parts.Select((part, index) =>
|
||||
{
|
||||
var partStart = part.LastIndexOf('/') + 1;
|
||||
return partStart > 0 ? $"{part.Substring(0, partStart)}/[{namePrefix}{index}]={part.Substring(partStart)}" : $"[{namePrefix}{index}]={part.Substring(partStart)}";
|
||||
})),
|
||||
string.Join(separator, parts.Select((part, index) => $"[{namePrefix}{index}]")));
|
||||
}
|
||||
|
||||
public static LocalizedString ParseInputTypes(LocalizedString str)
|
||||
{
|
||||
return new InputTypeLString(str);
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariable(string tag, string varName, LocalizedString value, FormatCapitals formatCapitals = FormatCapitals.No)
|
||||
{
|
||||
return GetWithVariable(tag.ToIdentifier(), varName.ToIdentifier(), value, formatCapitals);
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariable(Identifier tag, Identifier varName, LocalizedString value, FormatCapitals formatCapitals = FormatCapitals.No)
|
||||
{
|
||||
return GetWithVariables(tag, (varName, value));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(string tag, params (string Key, string Value)[] replacements)
|
||||
{
|
||||
return GetWithVariables(
|
||||
tag.ToIdentifier(),
|
||||
replacements.Select(kv =>
|
||||
(kv.Key.ToIdentifier(),
|
||||
(LocalizedString)new RawLString(kv.Value),
|
||||
FormatCapitals.No)));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(string tag, params (string Key, LocalizedString Value)[] replacements)
|
||||
{
|
||||
return GetWithVariables(
|
||||
tag.ToIdentifier(),
|
||||
replacements.Select(kv =>
|
||||
(kv.Key.ToIdentifier(),
|
||||
kv.Value,
|
||||
FormatCapitals.No)));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(string tag, params (string Key, LocalizedString Value, FormatCapitals FormatCapitals)[] replacements)
|
||||
{
|
||||
return GetWithVariables(
|
||||
tag.ToIdentifier(),
|
||||
replacements.Select(kv =>
|
||||
(kv.Key.ToIdentifier(),
|
||||
kv.Value,
|
||||
kv.FormatCapitals)));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(string tag, params (string Key, string Value, FormatCapitals FormatCapitals)[] replacements)
|
||||
{
|
||||
return GetWithVariables(
|
||||
tag.ToIdentifier(),
|
||||
replacements.Select(kv =>
|
||||
(kv.Key.ToIdentifier(),
|
||||
(LocalizedString)new RawLString(kv.Value),
|
||||
kv.FormatCapitals)));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(Identifier tag, params (Identifier Key, LocalizedString Value)[] replacements)
|
||||
{
|
||||
return GetWithVariables(tag, replacements.Select(kv => (kv.Key, kv.Value, FormatCapitals.No)));
|
||||
}
|
||||
|
||||
public static LocalizedString GetWithVariables(Identifier tag, IEnumerable<(Identifier, LocalizedString, FormatCapitals)> replacements)
|
||||
{
|
||||
return new ReplaceLString(new TagLString(tag), StringComparison.OrdinalIgnoreCase, replacements);
|
||||
}
|
||||
|
||||
public static void ConstructDescription(ref LocalizedString description, XElement descriptionElement)
|
||||
{
|
||||
/*
|
||||
<Description tag="talentdescription.simultaneousskillgain">
|
||||
<Replace tag="[skillname1]" value="skillname.helm"/>
|
||||
<Replace tag="[skillname2]" value="skillname.weapons"/>
|
||||
<Replace tag="[somevalue]" value="45.3"/>
|
||||
</Description>
|
||||
*/
|
||||
|
||||
LocalizedString extraDescriptionLine = Get(descriptionElement.GetAttributeIdentifier("tag", Identifier.Empty));
|
||||
foreach (XElement replaceElement in descriptionElement.Elements())
|
||||
{
|
||||
if (replaceElement.NameAsIdentifier() != "replace") { continue; }
|
||||
|
||||
Identifier tag = replaceElement.GetAttributeIdentifier("tag", Identifier.Empty);
|
||||
string[] replacementValues = replaceElement.GetAttributeStringArray("value", Array.Empty<string>());
|
||||
LocalizedString replacementValue = string.Empty;
|
||||
for (int i = 0; i < replacementValues.Length; i++)
|
||||
{
|
||||
replacementValue += Get(replacementValues[i]).Fallback(replacementValues[i]);
|
||||
if (i < replacementValues.Length - 1)
|
||||
{
|
||||
replacementValue += ", ";
|
||||
}
|
||||
}
|
||||
if (replaceElement.Attribute("color") != null)
|
||||
{
|
||||
string colorStr = replaceElement.GetAttributeString("color", "255,255,255,255");
|
||||
replacementValue = $"‖color:{colorStr}‖" + replacementValue + "‖color:end‖";
|
||||
}
|
||||
extraDescriptionLine = extraDescriptionLine.Replace(tag, replacementValue);
|
||||
}
|
||||
if (!(description is RawLString { Value: "" })) { description += "\n"; } //TODO: this is cursed
|
||||
description += extraDescriptionLine;
|
||||
}
|
||||
|
||||
public static LocalizedString GetServerMessage(string serverMessage)
|
||||
{
|
||||
return new ServerMsgLString(serverMessage);
|
||||
}
|
||||
|
||||
public static LocalizedString Capitalize(this LocalizedString str)
|
||||
{
|
||||
return new CapitalizeLString(str);
|
||||
}
|
||||
|
||||
public static void IncrementLanguageVersion()
|
||||
{
|
||||
LanguageVersion++;
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void CheckForDuplicates(LanguageIdentifier lang)
|
||||
{
|
||||
if (!TextPacks.ContainsKey(lang))
|
||||
{
|
||||
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
|
||||
return;
|
||||
}
|
||||
|
||||
int packIndex = 0;
|
||||
foreach (TextPack textPack in TextPacks[lang])
|
||||
{
|
||||
textPack.CheckForDuplicates(packIndex);
|
||||
packIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteToCSV()
|
||||
{
|
||||
LanguageIdentifier lang = DefaultLanguage;
|
||||
|
||||
if (!TextPacks.ContainsKey(lang))
|
||||
{
|
||||
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
|
||||
return;
|
||||
}
|
||||
|
||||
int packIndex = 0;
|
||||
foreach (TextPack textPack in TextPacks[lang])
|
||||
{
|
||||
textPack.WriteToCSV(packIndex);
|
||||
packIndex++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public readonly struct LanguageIdentifier
|
||||
{
|
||||
public static readonly LanguageIdentifier None = "None".ToLanguageIdentifier();
|
||||
|
||||
public readonly Identifier Value;
|
||||
public int ValueHash => Value.GetHashCode();
|
||||
public LanguageIdentifier(Identifier value) { Value = value; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is LanguageIdentifier other) { return this == other; }
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => ValueHash;
|
||||
|
||||
public static bool operator ==(LanguageIdentifier a, LanguageIdentifier b) => a.Value == b.Value;
|
||||
public static bool operator !=(LanguageIdentifier a, LanguageIdentifier b) => !(a==b);
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
|
||||
public static class LanguageIdentifierExtensions
|
||||
{
|
||||
public static LanguageIdentifier ToLanguageIdentifier(this Identifier identifier)
|
||||
{
|
||||
return new LanguageIdentifier(identifier);
|
||||
}
|
||||
|
||||
public static LanguageIdentifier ToLanguageIdentifier(this string str)
|
||||
{
|
||||
return str.ToIdentifier().ToLanguageIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
public class TextPack
|
||||
{
|
||||
public readonly TextFile ContentFile;
|
||||
|
||||
public readonly LanguageIdentifier Language;
|
||||
|
||||
public readonly ImmutableDictionary<Identifier, ImmutableArray<string>> Texts;
|
||||
public readonly string TranslatedName;
|
||||
public readonly bool NoWhitespace;
|
||||
|
||||
public TextPack(TextFile file, ContentXElement mainElement, LanguageIdentifier language)
|
||||
{
|
||||
ContentFile = file;
|
||||
|
||||
var languageName = mainElement.GetAttributeIdentifier("language", TextManager.DefaultLanguage.Value);
|
||||
Language = language;
|
||||
TranslatedName = mainElement.GetAttributeString("translatedname", languageName.Value);
|
||||
NoWhitespace = mainElement.GetAttributeBool("nowhitespace", false);
|
||||
|
||||
Dictionary<Identifier, List<string>> texts = new Dictionary<Identifier, List<string>>();
|
||||
foreach (var element in mainElement.Elements())
|
||||
{
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
if (!texts.ContainsKey(elemName)) { texts.Add(elemName, new List<string>()); }
|
||||
texts[elemName].Add(element.ElementInnerText()
|
||||
.Replace(@"\n", "\n")
|
||||
.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">")
|
||||
.Replace(""", "\"")
|
||||
.Replace("'", "'"));
|
||||
}
|
||||
Texts = texts.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public void CheckForDuplicates(int index)
|
||||
{
|
||||
Dictionary<Identifier, int> tagCounts = new Dictionary<Identifier, int>();
|
||||
Dictionary<string, int> contentCounts = new Dictionary<string, int>();
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(ContentFile.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
foreach (var subElement in doc.Root.Elements())
|
||||
{
|
||||
Identifier infoName = subElement.NameAsIdentifier();
|
||||
if (!tagCounts.ContainsKey(infoName))
|
||||
{
|
||||
tagCounts.Add(infoName, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
tagCounts[infoName] += 1;
|
||||
}
|
||||
|
||||
string infoContent = subElement.Value;
|
||||
if (string.IsNullOrEmpty(infoContent)) continue;
|
||||
if (!contentCounts.ContainsKey(infoContent))
|
||||
{
|
||||
contentCounts.Add(infoContent, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentCounts[infoContent] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Language: " + Language);
|
||||
sb.AppendLine();
|
||||
sb.Append("Duplicate tags:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
|
||||
for (int i = 0; i < tagCounts.Keys.Count; i++)
|
||||
{
|
||||
if (tagCounts[Texts.Keys.ElementAt(i)] > 1)
|
||||
{
|
||||
sb.Append(Texts.Keys.ElementAt(i) + " | Count: " + tagCounts[Texts.Keys.ElementAt(i)]);
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.Append("Duplicate content:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
|
||||
for (int i = 0; i < contentCounts.Keys.Count; i++)
|
||||
{
|
||||
if (contentCounts[contentCounts.Keys.ElementAt(i)] > 1)
|
||||
{
|
||||
sb.Append(contentCounts.Keys.ElementAt(i) + " | Count: " + contentCounts[contentCounts.Keys.ElementAt(i)]);
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
Barotrauma.IO.File.WriteAllText($"duplicate_{Language.ToString().ToLower()}_{index}.txt", sb.ToString());
|
||||
}
|
||||
|
||||
public void WriteToCSV(int index)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < Texts.Count; i++)
|
||||
{
|
||||
Identifier key = Texts.Keys.ElementAt(i);
|
||||
Texts.TryGetValue(key, out ImmutableArray<string> infoList);
|
||||
|
||||
for (int j = 0; j < infoList.Length; j++)
|
||||
{
|
||||
sb.Append(key); // ID
|
||||
sb.Append('*');
|
||||
sb.Append(infoList[j]); // Original
|
||||
sb.Append('*');
|
||||
// Translated
|
||||
sb.Append('*');
|
||||
// Comments
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
Barotrauma.IO.File.WriteAllText($"csv_{Language.ToString().ToLower()}_{index}.csv", sb.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user