Files
RobustToolbox/Robust.Shared/Localization/LocalizationManager.Functions.cs
lzk e170bf1ad2 genetive case (#6045)
* dative

* slipped it

* slipped it twice

* 1

* Update _engine_lib.ftl
2025-06-23 22:47:50 +02:00

479 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Linguini.Bundle;
using Linguini.Shared.Types.Bundle;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Localization;
using Robust.Shared.Maths;
namespace Robust.Shared.Localization
{
internal abstract partial class LocalizationManager
{
private static readonly Regex RegexWordMatch = new Regex(@"\w+");
private void AddBuiltInFunctions(FluentBundle bundle)
{
// Grammatical gender / pronouns
AddCtxFunction(bundle, "GENDER", FuncGender);
AddCtxFunction(bundle, "SUBJECT", FuncSubject);
AddCtxFunction(bundle, "OBJECT", FuncObject);
AddCtxFunction(bundle, "DAT-OBJ", FuncDatObj);
AddCtxFunction(bundle, "GENITIVE", FuncGenitive);
AddCtxFunction(bundle, "POSS-ADJ", FuncPossAdj);
AddCtxFunction(bundle, "POSS-PRONOUN", FuncPossPronoun);
AddCtxFunction(bundle, "REFLEXIVE", FuncReflexive);
AddCtxFunction(bundle, "COUNTER", FuncCounter);
// Conjugation
AddCtxFunction(bundle, "CONJUGATE-BE", FuncConjugateBe);
AddCtxFunction(bundle, "CONJUGATE-HAVE", FuncConjugateHave);
AddCtxFunction(bundle, "CONJUGATE-BASIC", FuncConjugateBasic);
// Proper nouns
AddCtxFunction(bundle, "PROPER", FuncProper);
AddCtxFunction(bundle, "THE", FuncThe);
// Misc
AddCtxFunction(bundle, "ATTRIB", args => FuncAttrib(bundle, args));
AddCtxFunction(bundle, "CAPITALIZE", FuncCapitalize);
AddCtxFunction(bundle, "INDEFINITE", FuncIndefinite);
}
/// <summary>
/// Returns the name of the entity passed in, prepended with "the" if it is not a proper noun.
/// </summary>
private ILocValue FuncThe(LocArgs args)
{
return new LocValueString(GetString("zzzz-the", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the string passed in, with the first letter capitalized.
/// </summary>
private ILocValue FuncCapitalize(LocArgs args)
{
var input = args.Args[0].Format(new LocContext());
if (!String.IsNullOrEmpty(input))
return new LocValueString(input[0].ToString().ToUpper() + input.Substring(1));
else return new LocValueString("");
}
private static readonly string[] IndefExceptions = { "euler", "heir", "honest" };
private static readonly char[] IndefCharList = { 'a', 'e', 'd', 'h', 'i', 'l', 'm', 'n', 'o', 'r', 's', 'x' };
private static readonly Regex[] IndefRegexes =
{
new ("^e[uw]"),
new ("^onc?e\b"),
new ("^uni([^nmd]|mo)"),
new ("^u[bcfhjkqrst][aeiou]")
};
private static readonly Regex IndefRegexFjo =
new("(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][A-Z]");
private static readonly Regex IndefRegexU = new("^U[NK][AIEO]");
private static readonly Regex IndefRegexY =
new("^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)");
private static readonly char[] IndefVowels = { 'a', 'e', 'i', 'o', 'u' };
private ILocValue FuncIndefinite(LocArgs args)
{
ILocValue val = args.Args[0];
if (val.Value == null)
return new LocValueString("an");
string? word;
string? input;
if (val.Value is EntityUid entity)
{
if (TryGetEntityLocAttrib(entity, "indefinite", out var indef))
return new LocValueString(indef);
input = _entMan.GetComponent<MetaDataComponent>(entity).EntityName;
}
else
{
input = val.Format(new LocContext());
}
if (String.IsNullOrEmpty(input))
return new LocValueString("");
var a = new LocValueString("a");
var an = new LocValueString("an");
var m = RegexWordMatch.Match(input);
if (m.Success)
{
word = m.Groups[0].Value;
}
else
{
return an;
}
var wordi = word.ToLower();
if (IndefExceptions.Any(anword => wordi.StartsWith(anword)))
{
return an;
}
if (wordi.StartsWith("hour") && !wordi.StartsWith("houri"))
return an;
if (wordi.Length == 1)
{
return wordi.IndexOfAny(IndefCharList) == 0 ? an : a;
}
if (IndefRegexFjo.Match(word)
.Success)
{
return an;
}
foreach (var regex in IndefRegexes)
{
if (regex.IsMatch(wordi))
return a;
}
if (IndefRegexU.IsMatch(word))
{
return a;
}
if (word == word.ToUpper())
{
return wordi.IndexOfAny(IndefCharList) == 0 ? an : a;
}
if (wordi.IndexOfAny(IndefVowels) == 0)
{
return an;
}
return IndefRegexY.IsMatch(wordi) ? an : a;
}
/// <summary>
/// Returns the gender of the entity passed in; either Male, Female, Neuter or Epicene.
/// </summary>
private ILocValue FuncGender(LocArgs args)
{
if (args.Args.Count < 1) return new LocValueString(nameof(Gender.Neuter));
ILocValue entity0 = args.Args[0];
if (entity0.Value is EntityUid entity)
{
if (_entMan.TryGetComponent(entity, out GrammarComponent? grammar) && grammar.Gender.HasValue)
{
return new LocValueString(grammar.Gender.Value.ToString().ToLowerInvariant());
}
if (TryGetEntityLocAttrib(entity, "gender", out var gender))
{
return new LocValueString(gender);
}
}
return new LocValueString(nameof(Gender.Neuter));
}
/// <summary>
/// Returns the respective subject pronoun (he, she, they, it) for the entity's gender.
/// </summary>
private ILocValue FuncSubject(LocArgs args)
{
return new LocValueString(GetString("zzzz-subject-pronoun", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective object pronoun (him, her, them, it) for the entity's gender.
/// </summary>
private ILocValue FuncObject(LocArgs args)
{
return new LocValueString(GetString("zzzz-object-pronoun", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the dative form pronoun for the entity's gender.
/// This method is intended for languages with a dative case, where indirect objects
/// (e.g., "to him," "for her") require specific forms. Not applicable for en-US locale.
/// </summary>
private ILocValue FuncDatObj(LocArgs args)
{
return new LocValueString(GetString("zzzz-dat-object", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective genitive form (pronoun or possessive adjective) for the entity's gender.
/// This is used in languages with a genitive case to indicate possession or related relationships,
/// e.g., "у него" (Russian), "seines Vaters" (German).
private ILocValue FuncGenitive(LocArgs args)
{
return new LocValueString(GetString("zzzz-genitive", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective possessive adjective (his, her, their, its) for the entity's gender.
/// </summary>
private ILocValue FuncPossAdj(LocArgs args)
{
return new LocValueString(GetString("zzzz-possessive-adjective", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective possessive pronoun (his, hers, theirs, its) for the entity's gender.
/// </summary>
private ILocValue FuncPossPronoun(LocArgs args)
{
return new LocValueString(GetString("zzzz-possessive-pronoun", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective reflexive pronoun (himself, herself, themselves, itself) for the entity's gender.
/// </summary>
private ILocValue FuncReflexive(LocArgs args)
{
return new LocValueString(GetString("zzzz-reflexive-pronoun", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the counter or measure word for the entity. Not used in English, common in East Asian languages.
/// </summary>
private ILocValue FuncCounter(LocArgs args)
{
if (args.Args.Count < 1) return new LocValueString(GetString("zzzz-counter-default"));
ILocValue entity0 = args.Args[0];
if (entity0.Value is EntityUid entity)
{
if (TryGetEntityLocAttrib(entity, "counter", out var counter))
{
return new LocValueString(counter);
}
}
return new LocValueString(GetString("zzzz-counter-default"));
}
/// <summary>
/// Returns the respective conjugated form of "to be" (is for male/female/neuter, are for epicene) for the entity's gender.
/// </summary>
private ILocValue FuncConjugateBe(LocArgs args)
{
return new LocValueString(GetString("zzzz-conjugate-be", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective conjugated form of "to have" (has for male/female/neuter, have for epicene) for the entity's gender.
/// </summary>
private ILocValue FuncConjugateHave(LocArgs args)
{
return new LocValueString(GetString("zzzz-conjugate-have", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the basic conjugated form of a verb. The first string argument is the base verb, the second string argument is the form
/// for he/she/it.
/// e.g. run -> he runs/she runs/they run/it runs
/// </summary>
private ILocValue FuncConjugateBasic(LocArgs args)
{
var first = ((LocValueString)args.Args[1]).Value;
var second = ((LocValueString)args.Args[2]).Value;
return new LocValueString(GetString("zzzz-conjugate-basic", ("ent", args.Args[0]), ("first", first), ("second", second)));
}
private ILocValue FuncAttrib(FluentBundle bundle, LocArgs args)
{
if (args.Args.Count < 2) return new LocValueString("other");
ILocValue entity0 = args.Args[0];
if (entity0.Value is EntityUid entity)
{
ILocValue attrib0 = args.Args[1];
if (TryGetEntityLocAttrib(entity, attrib0.Format(new LocContext(bundle)), out var attrib))
{
return new LocValueString(attrib);
}
}
return new LocValueString("other");
}
/// <summary>
/// Returns whether the passed in entity's name is proper or not.
/// </summary>
private ILocValue FuncProper(LocArgs args)
{
if (args.Args.Count < 1) return new LocValueString("false");
ILocValue entity0 = args.Args[0];
if (entity0.Value is EntityUid entity)
{
if (_entMan.TryGetComponent(entity, out GrammarComponent? grammar) && grammar.ProperNoun.HasValue)
{
return new LocValueString(grammar.ProperNoun.Value.ToString().ToLowerInvariant());
}
if (TryGetEntityLocAttrib(entity, "proper", out var proper))
{
return new LocValueString(proper);
}
}
return new LocValueString("false");
}
private void AddCtxFunction(FluentBundle ctx, string name, LocFunction function)
{
ctx.AddFunctionOverriding(name, (args, options) => CallFunction(function, ctx, args, options));
}
private IFluentType CallFunction(
LocFunction function,
FluentBundle bundle,
IList<IFluentType> positionalArgs,
IDictionary<string, IFluentType> namedArgs)
{
var args = new ILocValue[positionalArgs.Count];
for (var i = 0; i < args.Length; i++)
{
args[i] = positionalArgs[i].ToLocValue();
}
var options = new Dictionary<string, ILocValue>(namedArgs.Count);
foreach (var (k, v) in namedArgs)
{
options.Add(k, v.ToLocValue());
}
var argStruct = new LocArgs(args, options);
return function.Invoke(argStruct).FluentFromVal(new LocContext(bundle));
}
public void AddFunction(CultureInfo culture, string name, LocFunction function)
{
var bundle = _contexts[culture];
bundle.AddFunctionOverriding(name, (args, options)
=> CallFunction(function, bundle, args, options));
}
}
internal sealed class FluentLocWrapperType : IFluentType
{
public readonly ILocValue WrappedValue;
private readonly LocContext _context;
public FluentLocWrapperType(ILocValue wrappedValue, LocContext context)
{
WrappedValue = wrappedValue;
_context = context;
}
public string AsString()
{
return WrappedValue.Format(_context);
}
public bool IsError()
{
return false;
}
public bool Matches(IFluentType other, IScope scope)
{
if (other is FluentLocWrapperType otherWrapper)
{
return (WrappedValue, otherWrapper.WrappedValue) switch
{
(LocValueNone, LocValueNone) => true,
(LocValueDateTime l, LocValueDateTime d) => l.Value.Equals(d.Value),
(LocValueTimeSpan l, LocValueTimeSpan d) => l.Value.Equals(d.Value),
(LocValueNumber l, LocValueNumber d) => l.Value.Equals(d.Value),
(LocValueString l, LocValueString d) => l.Value.Equals(d.Value),
(LocValueEntity l, LocValueEntity d) => l.Value.Equals(d.Value),
({ } l, { } d) => Equals(l, d),
_ => false,
};
}
return false;
}
public IFluentType Copy()
{
return this;
}
}
static class LinguiniAdapter
{
internal static ILocValue ToLocValue(this IFluentType arg)
{
return arg switch
{
FluentNone => new LocValueNone(""),
FluentNumber number => new LocValueNumber(number),
FluentString str => new LocValueString(str),
FluentLocWrapperType value => value.WrappedValue,
_ => throw new ArgumentOutOfRangeException(nameof(arg)),
};
}
public static IFluentType FluentFromObject(this object obj, LocContext context)
{
return obj switch
{
ILocValue wrap => new FluentLocWrapperType(wrap, context),
EntityUid entity => new FluentLocWrapperType(new LocValueEntity(entity), context),
IFluentEntityUid entity => new FluentLocWrapperType(new LocValueEntity(entity.FluentOwner), context),
DateTime dateTime => new FluentLocWrapperType(new LocValueDateTime(dateTime), context),
TimeSpan timeSpan => new FluentLocWrapperType(new LocValueTimeSpan(timeSpan), context),
Color color => (FluentString)color.ToHex(),
bool or Enum => (FluentString)obj.ToString()!.ToLowerInvariant(),
string str => (FluentString)str,
byte num => (FluentNumber)num,
sbyte num => (FluentNumber)num,
short num => (FluentNumber)num,
ushort num => (FluentNumber)num,
int num => (FluentNumber)num,
uint num => (FluentNumber)num,
long num => (FluentNumber)num,
ulong num => (FluentNumber)num,
double dbl => (FluentNumber)dbl,
float dbl => (FluentNumber)dbl,
_ => (FluentString)obj.ToString()!,
};
}
public static IFluentType FluentFromVal(this ILocValue locValue, LocContext context)
{
return locValue switch
{
LocValueNone => FluentNone.None,
LocValueNumber number => (FluentNumber)number.Value,
LocValueString str => (FluentString)str.Value,
_ => new FluentLocWrapperType(locValue, context),
};
}
}
internal interface IFluentEntityUid
{
internal EntityUid FluentOwner { get; }
};
}