Shared/Localizations: Add translatable Units formatting (#5063)
This commit is contained in:
@@ -33,6 +33,7 @@ namespace Content.Shared.Localizations
|
||||
loc.AddFunction(culture, "PRESSURE", FormatPressure);
|
||||
loc.AddFunction(culture, "POWERWATTS", FormatPowerWatts);
|
||||
loc.AddFunction(culture, "POWERJOULES", FormatPowerJoules);
|
||||
loc.AddFunction(culture, "UNITS", FormatUnits);
|
||||
loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args));
|
||||
loc.AddFunction(culture, "LOC", FormatLoc);
|
||||
}
|
||||
@@ -85,5 +86,45 @@ namespace Content.Shared.Localizations
|
||||
{
|
||||
return FormatUnitsGeneric(args, "zzzz-fmt-power-joules");
|
||||
}
|
||||
|
||||
private static ILocValue FormatUnits(LocArgs args)
|
||||
{
|
||||
if (!Units.Types.TryGetValue(((LocValueString) args.Args[0]).Value, out var ut))
|
||||
throw new ArgumentException($"Unknown unit type {((LocValueString) args.Args[0]).Value}");
|
||||
|
||||
var fmtstr = ((LocValueString) args.Args[1]).Value;
|
||||
|
||||
double max = Double.NegativeInfinity;
|
||||
var iargs = new double[args.Args.Count - 1];
|
||||
for (var i = 2; i < args.Args.Count; i++)
|
||||
{
|
||||
var n = ((LocValueNumber) args.Args[i]).Value;
|
||||
if (n > max)
|
||||
max = n;
|
||||
|
||||
iargs[i - 2] = n;
|
||||
}
|
||||
|
||||
if (!ut!.TryGetUnit(max, out var mu))
|
||||
throw new ArgumentException("Unit out of range for type");
|
||||
|
||||
var fargs = new object[iargs.Length];
|
||||
|
||||
for (var i = 0; i < iargs.Length; i++)
|
||||
fargs[i] = iargs[i] * mu.Factor;
|
||||
|
||||
fargs[^1] = Loc.GetString($"units-{mu.Unit.ToLower()}");
|
||||
|
||||
// Before anyone complains about "{"+"${...}", at least it's better than MS's approach...
|
||||
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting#escaping-braces
|
||||
//
|
||||
// Note that the closing brace isn't replaced so that format specifiers can be applied.
|
||||
var res = String.Format(
|
||||
fmtstr.Replace("{UNIT", "{" + $"{fargs.Length - 1}"),
|
||||
fargs
|
||||
);
|
||||
|
||||
return new LocValueString(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
131
Content.Shared/Localizations/Units.cs
Normal file
131
Content.Shared/Localizations/Units.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Localizations
|
||||
{
|
||||
public static class Units
|
||||
{
|
||||
public sealed class TypeTable
|
||||
{
|
||||
public readonly Entry[] E;
|
||||
|
||||
public TypeTable(params Entry[] e) => E = e;
|
||||
|
||||
public sealed class Entry
|
||||
{
|
||||
// Any item within [Min, Max) is considered to be in-range
|
||||
// of this Entry.
|
||||
public readonly (double? Min, double? Max) Range;
|
||||
|
||||
// Factor is a number that the value will be multiplied by
|
||||
// to adjust it in to the proper range.
|
||||
public readonly double Factor;
|
||||
|
||||
// Unit is an ID for Fluent. All Units are prefixed with
|
||||
// "unit-" internally. Usually follows the format $"{unit-abbrev}-{prefix}".
|
||||
//
|
||||
// Example: "si-g" is actually processed as "units-si-g"
|
||||
//
|
||||
// As a matter of style, units for values less than 1 (i.e. mW)
|
||||
// should have two dashes before their prefix.
|
||||
public readonly string Unit;
|
||||
|
||||
public Entry((double?, double?) range, double factor, string unit)
|
||||
{
|
||||
Range = range;
|
||||
Factor = factor;
|
||||
Unit = unit;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetUnit(double val, [NotNullWhen(true)] out Entry? winner)
|
||||
{
|
||||
Entry? w = default!;
|
||||
foreach (var e in E)
|
||||
if ((e.Range.Min == null || e.Range.Min <= val) && (e.Range.Max == null || val < e.Range.Max))
|
||||
w = e;
|
||||
|
||||
winner = w;
|
||||
return w != null;
|
||||
}
|
||||
|
||||
public string Format(double val)
|
||||
{
|
||||
if (TryGetUnit(val, out var w))
|
||||
return (val * w.Factor).ToString() + " " + w.Unit;
|
||||
|
||||
return val.ToString();
|
||||
}
|
||||
|
||||
public string Format(double val, string fmt)
|
||||
{
|
||||
if (TryGetUnit(val, out var w))
|
||||
return (val * w.Factor).ToString(fmt) + " " + w.Unit;
|
||||
|
||||
return val.ToString(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly TypeTable Generic = new TypeTable
|
||||
(
|
||||
// Table layout. Fite me.
|
||||
new TypeTable.Entry(range: ( null, 1e-24), factor: 1e24, unit: "si--y"),
|
||||
new TypeTable.Entry(range: (1e-24, 1e-21), factor: 1e21, unit: "si--z"),
|
||||
new TypeTable.Entry(range: (1e-21, 1e-18), factor: 1e18, unit: "si--a"),
|
||||
new TypeTable.Entry(range: (1e-18, 1e-15), factor: 1e15, unit: "si--f"),
|
||||
new TypeTable.Entry(range: (1e-15, 1e-12), factor: 1e12, unit: "si--p"),
|
||||
new TypeTable.Entry(range: (1e-12, 1e-9), factor: 1e9, unit: "si--n"),
|
||||
new TypeTable.Entry(range: ( 1e-9, 1e-3), factor: 1e6, unit: "si--u"),
|
||||
new TypeTable.Entry(range: ( 1e-3, 1), factor: 1e3, unit: "si--m"),
|
||||
new TypeTable.Entry(range: ( 1, 1000), factor: 1, unit: "si"),
|
||||
new TypeTable.Entry(range: ( 1000, 1e6), factor: 1e-4, unit: "si-k"),
|
||||
new TypeTable.Entry(range: ( 1e6, 1e9), factor: 1e-6, unit: "si-m"),
|
||||
new TypeTable.Entry(range: ( 1e9, 1e12), factor: 1e-9, unit: "si-g"),
|
||||
new TypeTable.Entry(range: ( 1e12, 1e15), factor: 1e-12, unit: "si-t"),
|
||||
new TypeTable.Entry(range: ( 1e15, 1e18), factor: 1e-15, unit: "si-p"),
|
||||
new TypeTable.Entry(range: ( 1e18, 1e21), factor: 1e-18, unit: "si-e"),
|
||||
new TypeTable.Entry(range: ( 1e21, 1e24), factor: 1e-21, unit: "si-z"),
|
||||
new TypeTable.Entry(range: ( 1e24, null), factor: 1e-24, unit: "si-y")
|
||||
);
|
||||
|
||||
// N.B. We use kPa internally, so this is shifted one order of magnitude down.
|
||||
public static readonly TypeTable Pressure = new TypeTable
|
||||
(
|
||||
new TypeTable.Entry(range: (null, 1e-6), factor: 1e9, unit: "u--pascal"),
|
||||
new TypeTable.Entry(range: (1e-6, 1e-3), factor: 1e6, unit: "m--pascal"),
|
||||
new TypeTable.Entry(range: (1e-3, 1), factor: 1e3, unit: "pascal"),
|
||||
new TypeTable.Entry(range: ( 1, 1000), factor: 1, unit: "k-pascal"),
|
||||
new TypeTable.Entry(range: (1000, 1e6), factor: 1e-4, unit: "M-pascal"),
|
||||
new TypeTable.Entry(range: ( 1e6, null), factor: 1e-6, unit: "G-pascal")
|
||||
);
|
||||
|
||||
public static readonly TypeTable Power = new TypeTable
|
||||
(
|
||||
new TypeTable.Entry(range: (null, 1e-3), factor: 1e6, unit: "u--watt"),
|
||||
new TypeTable.Entry(range: (1e-3, 1), factor: 1e3, unit: "m--watt"),
|
||||
new TypeTable.Entry(range: ( 1, 1000), factor: 1, unit: "watt"),
|
||||
new TypeTable.Entry(range: (1000, 1e6), factor: 1e-4, unit: "k-watt"),
|
||||
new TypeTable.Entry(range: ( 1e6, 1e9), factor: 1e-6, unit: "m-watt"),
|
||||
new TypeTable.Entry(range: ( 1e9, null), factor: 1e-9, unit: "g-watt")
|
||||
);
|
||||
|
||||
public static readonly TypeTable Energy = new TypeTable
|
||||
(
|
||||
new TypeTable.Entry(range: (null, 1e-3), factor: 1e6, unit: "u--joule"),
|
||||
new TypeTable.Entry(range: (1e-3, 1), factor: 1e3, unit: "m--joule"),
|
||||
new TypeTable.Entry(range: ( 1, 1000), factor: 1, unit: "joule"),
|
||||
new TypeTable.Entry(range: (1000, 1e6), factor: 1e-4, unit: "k-joule"),
|
||||
new TypeTable.Entry(range: ( 1e6, 1e9), factor: 1e-6, unit: "m-joule"),
|
||||
new TypeTable.Entry(range: ( 1e9, null), factor: 1e-9, unit: "g-joule")
|
||||
);
|
||||
|
||||
public readonly static Dictionary<string, TypeTable> Types = new Dictionary<string, TypeTable>
|
||||
{
|
||||
["generic"] = Generic!,
|
||||
["pressure"] = Pressure!,
|
||||
["power"] = Power!,
|
||||
["energy"] = Energy!
|
||||
};
|
||||
}
|
||||
}
|
||||
80
Resources/Locale/en-US/_units.ftl
Normal file
80
Resources/Locale/en-US/_units.ftl
Normal file
@@ -0,0 +1,80 @@
|
||||
## Standard SI prefixes
|
||||
units-si--y = y
|
||||
units-si--z = z
|
||||
units-si--a = a
|
||||
units-si--f = f
|
||||
units-si--p = p
|
||||
units-si--n = n
|
||||
units-si--u = µ
|
||||
units-si--m = m
|
||||
units-si = {""}
|
||||
units-si-k = k
|
||||
units-si-m = M
|
||||
units-si-g = G
|
||||
units-si-t = T
|
||||
units-si-p = P
|
||||
units-si-e = E
|
||||
units-si-z = Z
|
||||
units-si-y = Y
|
||||
|
||||
### Long form
|
||||
units-si--y-long = yocto
|
||||
units-si--z-long = zepto
|
||||
units-si--a-long = atto
|
||||
units-si--f-long = femto
|
||||
units-si--p-long = pico
|
||||
units-si--n-long = nnano
|
||||
units-si--u-long = micro
|
||||
units-si--m-long = milli
|
||||
units-si-long = {""}
|
||||
units-si-k-long = kilo
|
||||
units-si-m-long = mega
|
||||
units-si-g-long = giga
|
||||
units-si-t-long = tera
|
||||
units-si-p-long = peta
|
||||
units-si-e-long = exa
|
||||
units-si-z-long = zetta
|
||||
units-si-y-long = yotta
|
||||
|
||||
## Pascals (Pressure)
|
||||
units-u--pascal = µPa
|
||||
units-m--pascal = mPa
|
||||
units-pascal = Pa
|
||||
units-k-pascal = kPa
|
||||
units-m-pascal = MPa
|
||||
units-g-pascal = GPa
|
||||
|
||||
units-u--pascal-long = Micropascal
|
||||
units-m--pascal-long = Millipascal
|
||||
units-pascal-long = Pascal
|
||||
units-k-pascal-long = Kilopascal
|
||||
units-m-pascal-long = Megapascal
|
||||
units-g-pascal-long = Gigapascal
|
||||
|
||||
## Watts (Power)
|
||||
units-u--watt = µW
|
||||
units-m--watt = mW
|
||||
units-watt = W
|
||||
units-k-watt = kW
|
||||
units-m-watt = MW
|
||||
units-g-watt = GW
|
||||
|
||||
units-u--watt-long = Microwatt
|
||||
units-m--watt-long = Milliwatt
|
||||
units-watt-long = Watt
|
||||
units-k-watt-long = Kilowatt
|
||||
units-m-watt-long = Megawatt
|
||||
units-g-watt-long = Gigawatt
|
||||
|
||||
## Joule (Energy)
|
||||
units-u--joule = µJ
|
||||
units-m--joule = mJ
|
||||
units-joule = J
|
||||
units-k-joule = kJ
|
||||
units-m-joule = MJ
|
||||
|
||||
units-u--joule-long = Microjoule
|
||||
units-m--joule-long = Millijoule
|
||||
units-joule-long = Joule
|
||||
units-k-joule-long = Kilojoule
|
||||
units-m-joule-long = Megajoule
|
||||
Reference in New Issue
Block a user