Skip to content

Commit 472ccbd

Browse files
authored
Support localized SI prefixes, fix Russian (#663)
* Support localized SI prefixes, fix Russian The majority of AbbreviationsForPrefixes was adding Russian abbreviations. This is no longer necessary for unit abbreviations that simply prepend the Russian SI prefix. This change fixes many unit abbreviations in Russian that incorrectly used international SI prefixes instead of the Russian equivalents. - Add localization support to SI prefixes - Map Russian SI prefixes - Remove redundant JSON properties of AbbreviationsForPrefixes, not just Russian * Whitespace fix
1 parent 46021ee commit 472ccbd

File tree

17 files changed

+251
-216
lines changed

17 files changed

+251
-216
lines changed

CodeGen/Generators/QuantityJsonFilesParser.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,22 @@ private static void FixConversionFunctionsForDecimalValueTypes(Quantity quantity
6262
{
6363
foreach (var u in quantity.Units)
6464
// Use decimal for internal calculations if base type is not double, such as for long or int.
65+
{
6566
if (string.Equals(quantity.BaseType, "decimal", StringComparison.OrdinalIgnoreCase))
6667
{
6768
// Change any double literals like "1024d" to decimal literals "1024m"
6869
u.FromUnitToBaseFunc = u.FromUnitToBaseFunc.Replace("d", "m");
6970
u.FromBaseToUnitFunc = u.FromBaseToUnitFunc.Replace("d", "m");
7071
}
72+
}
7173
}
7274

7375
private static void AddPrefixUnits(Quantity quantity)
7476
{
7577
var unitsToAdd = new List<Unit>();
7678
foreach (var unit in quantity.Units)
77-
// "Kilo", "Nano" etc.
7879
foreach (var prefix in unit.Prefixes)
80+
{
7981
try
8082
{
8183
var prefixInfo = PrefixInfo.Entries[prefix];
@@ -94,29 +96,32 @@ private static void AddPrefixUnits(Quantity quantity)
9496
{
9597
throw new Exception($"Error parsing prefix {prefix} for unit {quantity.Name}.{unit.SingularName}.", e);
9698
}
99+
}
97100

98101
quantity.Units = quantity.Units.Concat(unitsToAdd).ToArray();
99102
}
100103

101104
/// <summary>
102105
/// Create unit abbreviations for a prefix unit, given a unit and the prefix.
103106
/// The unit abbreviations are either prefixed with the SI prefix or an explicitly configured abbreviation via
104-
/// <see cref="AbbreviationsForPrefixes" />.
107+
/// <see cref="Localization.AbbreviationsForPrefixes" />.
105108
/// </summary>
106109
private static Localization[] GetLocalizationForPrefixUnit(IEnumerable<Localization> localizations, PrefixInfo prefixInfo)
107110
{
108111
return localizations.Select(loc =>
109112
{
110-
if (loc.TryGetAbbreviationsForPrefix(prefixInfo.Prefix, out var unitAbbreviationsForPrefix))
113+
if (loc.TryGetAbbreviationsForPrefix(prefixInfo.Prefix, out string[] unitAbbreviationsForPrefix))
114+
{
111115
return new Localization
112116
{
113117
Culture = loc.Culture,
114118
Abbreviations = unitAbbreviationsForPrefix
115119
};
120+
}
116121

117122
// No prefix unit abbreviations are specified, so fall back to prepending the default SI prefix to each unit abbreviation:
118123
// kilo ("k") + meter ("m") => kilometer ("km")
119-
var prefix = prefixInfo.Abbreviation;
124+
var prefix = prefixInfo.GetPrefixForCultureOrSiPrefix(loc.Culture);
120125
unitAbbreviationsForPrefix = loc.Abbreviations.Select(unitAbbreviation => $"{prefix}{unitAbbreviation}").ToArray();
121126

122127
return new Localization

CodeGen/JsonTypes/Localization.cs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,22 @@
66

77
namespace CodeGen.JsonTypes
88
{
9+
/// <summary>
10+
/// Localization of a unit, such as unit abbreviations in different languages.
11+
/// </summary>
912
internal class Localization
1013
{
11-
// 0649 Field is never assigned to
12-
#pragma warning disable 0649
13-
14-
public string[] Abbreviations = Array.Empty<string>();
15-
1614
/// <summary>
17-
/// Unit abbreviations for prefixes of a unit.
18-
/// Typically, this is used for languages or units abbreviations where the default SI prefix ("k" for kilo etc) cannot simply be prepended
19-
/// to the abbreviations defined in <see cref="Localization.Abbreviations"/>.
15+
/// Gets the unit abbreviations for a prefix, if configured.
2016
/// </summary>
21-
/// <example>
22-
/// Duration.Second unit for Russian culture has "Abbreviations": [ "с", "сек" ] and "AbbreviationsForPrefixes": { "Nano": ["нс", "нсек"], "Micro": ["мкс", "мксек"], "Milli": ["мс", "мсек"] }
23-
/// </example>
24-
/// <remarks>One or more of properties with <see cref="Prefix"/> names should be assigned a string or a string[].</remarks>
25-
public JObject AbbreviationsForPrefixes;
26-
27-
public string Culture;
28-
29-
// 0649 Field is never assigned to
30-
#pragma warning restore 0649
31-
17+
/// <param name="prefix">The SI prefix.</param>
18+
/// <param name="unitAbbreviations">The configured unit abbreviations. Null if not configured.</param>
19+
/// <returns>True if configured, otherwise false.</returns>
20+
/// <exception cref="NotSupportedException">Unit abbreviations must be a string or an array of strings.</exception>
3221
public bool TryGetAbbreviationsForPrefix(Prefix prefix, out string[] unitAbbreviations)
3322
{
3423
if (AbbreviationsForPrefixes == null ||
35-
!AbbreviationsForPrefixes.TryGetValue(prefix.ToString(), out JToken value))
24+
!AbbreviationsForPrefixes.TryGetValue(prefix.ToString(), out var value))
3625
{
3726
unitAbbreviations = default;
3827
return false;
@@ -51,8 +40,39 @@ public bool TryGetAbbreviationsForPrefix(Prefix prefix, out string[] unitAbbrevi
5140
return true;
5241
}
5342
default:
54-
throw new NotSupportedException($"Expected AbbreviationsForPrefixes.{prefix} to be a string or an array of strings.");
43+
throw new NotSupportedException($"AbbreviationsForPrefixes.{prefix} must be a string or an array of strings, but was {value.Type}.");
5544
}
5645
}
46+
// 0649 Field is never assigned to
47+
#pragma warning disable 0649
48+
49+
/// <summary>
50+
/// The unit abbreviations. Can be empty for dimensionless units like Ratio.DecimalFraction.
51+
/// </summary>
52+
public string[] Abbreviations = Array.Empty<string>();
53+
54+
/// <summary>
55+
/// Explicit configuration of unit abbreviations for prefixes.
56+
/// This is typically used for languages or special unit abbreviations where you cannot simply prepend SI prefixes like
57+
/// "k" for kilo
58+
/// to the abbreviations defined in <see cref="Localization.Abbreviations" />.
59+
/// </summary>
60+
/// <example>
61+
/// Energy.ThermEc unit has "Abbreviations": "th (E.C.)" and "AbbreviationsForPrefixes": { "Deca": "Dth (E.C.)" } since
62+
/// the SI prefix for Deca is "Da" and "Dath (E.C.)" is not the conventional form.
63+
/// </example>
64+
/// <remarks>
65+
/// The unit abbreviation value can either be a string or an array of strings. Typically the number of abbreviations
66+
/// for a prefix matches that of "Abbreviations" array, but this is not required.
67+
/// </remarks>
68+
public JObject AbbreviationsForPrefixes;
69+
70+
/// <summary>
71+
/// The name of the culture this is a localization for.
72+
/// </summary>
73+
public string Culture;
74+
75+
// 0649 Field is never assigned to
76+
#pragma warning restore 0649
5777
}
5878
}

CodeGen/PrefixInfo.cs

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,95 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using CodeGen.JsonTypes;
45

56
namespace CodeGen
67
{
78
/// <summary>
8-
/// Information about a unit prefix and a static dictionary to look up prefixes.
9+
/// Information about a unit prefix and a static dictionary to look up prefixes.
910
/// </summary>
1011
internal class PrefixInfo
1112
{
12-
/// <summary>
13-
/// The unit prefix.
14-
/// </summary>
15-
public Prefix Prefix { get; }
16-
17-
/// <summary>
18-
/// The unit prefix abbreviation, such as "k" for kilo or "m" for milli.
19-
/// </summary>
20-
public string Abbreviation { get; }
21-
22-
/// <summary>
23-
/// C# expression for the multiplier to prefix the conversion function.
24-
/// </summary>
25-
/// <example>Kilo has "1e3" in order to multiply by 1000.</example>
26-
public string Factor { get; }
13+
private const string Russian = "ru-RU";
2714

2815
public static readonly IReadOnlyDictionary<Prefix, PrefixInfo> Entries = new[]
2916
{
3017
// Need to append 'd' suffix for double in order to later search/replace "d" with "m"
3118
// when creating decimal conversion functions in CodeGen.Generator.FixConversionFunctionsForDecimalValueTypes.
3219

3320
// SI prefixes
34-
new PrefixInfo(Prefix.Yocto, "y", "1e-24d"),
35-
new PrefixInfo(Prefix.Zepto, "z", "1e-21d"),
36-
new PrefixInfo(Prefix.Atto, "a", "1e-18d"),
37-
new PrefixInfo(Prefix.Femto, "f", "1e-15d"),
38-
new PrefixInfo(Prefix.Pico, "p", "1e-12d"),
39-
new PrefixInfo(Prefix.Nano, "n", "1e-9d"),
40-
new PrefixInfo(Prefix.Micro, "µ", "1e-6d"),
41-
new PrefixInfo(Prefix.Milli, "m", "1e-3d"),
42-
new PrefixInfo(Prefix.Centi, "c", "1e-2d"),
43-
new PrefixInfo(Prefix.Deci, "d", "1e-1d"),
44-
new PrefixInfo(Prefix.Deca, "da", "1e1d"),
45-
new PrefixInfo(Prefix.Hecto, "h", "1e2d"),
46-
new PrefixInfo(Prefix.Kilo, "k", "1e3d"),
47-
new PrefixInfo(Prefix.Mega, "M", "1e6d"),
48-
new PrefixInfo(Prefix.Giga, "G", "1e9d"),
49-
new PrefixInfo(Prefix.Tera, "T", "1e12d"),
50-
new PrefixInfo(Prefix.Peta, "P", "1e15d"),
51-
new PrefixInfo(Prefix.Exa, "E", "1e18d"),
52-
new PrefixInfo(Prefix.Zetta, "Z", "1e21d"),
53-
new PrefixInfo(Prefix.Yotta, "Y", "1e24d"),
21+
new PrefixInfo(Prefix.Yocto, "1e-24d", "y"),
22+
new PrefixInfo(Prefix.Zepto, "1e-21d", "z"),
23+
new PrefixInfo(Prefix.Atto, "1e-18d", "a", (Russian, "а")),
24+
new PrefixInfo(Prefix.Femto, "1e-15d", "f", (Russian, "ф")),
25+
new PrefixInfo(Prefix.Pico, "1e-12d", "p", (Russian, "п")),
26+
new PrefixInfo(Prefix.Nano, "1e-9d", "n", (Russian, "н")),
27+
new PrefixInfo(Prefix.Micro, "1e-6d", "µ", (Russian, "мк")),
28+
new PrefixInfo(Prefix.Milli, "1e-3d", "m", (Russian, "м")),
29+
new PrefixInfo(Prefix.Centi, "1e-2d", "c", (Russian, "с")),
30+
new PrefixInfo(Prefix.Deci, "1e-1d", "d", (Russian, "д")),
31+
new PrefixInfo(Prefix.Deca, "1e1d", "da", (Russian, "да")),
32+
new PrefixInfo(Prefix.Hecto, "1e2d", "h", (Russian, "г")),
33+
new PrefixInfo(Prefix.Kilo, "1e3d", "k", (Russian, "к")),
34+
new PrefixInfo(Prefix.Mega, "1e6d", "M", (Russian, "М")),
35+
new PrefixInfo(Prefix.Giga, "1e9d", "G", (Russian, "Г")),
36+
new PrefixInfo(Prefix.Tera, "1e12d", "T", (Russian, "Т")),
37+
new PrefixInfo(Prefix.Peta, "1e15d", "P", (Russian, "П")),
38+
new PrefixInfo(Prefix.Exa, "1e18d", "E", (Russian, "Э")),
39+
new PrefixInfo(Prefix.Zetta, "1e21d", "Z"),
40+
new PrefixInfo(Prefix.Yotta, "1e24d", "Y"),
5441

5542
// Binary prefixes
56-
new PrefixInfo(Prefix.Kibi, "Ki", $"1024d"),
57-
new PrefixInfo(Prefix.Mebi, "Mi", $"(1024d * 1024)"),
58-
new PrefixInfo(Prefix.Gibi, "Gi", $"(1024d * 1024 * 1024)"),
59-
new PrefixInfo(Prefix.Tebi, "Ti", $"(1024d * 1024 * 1024 * 1024)"),
60-
new PrefixInfo(Prefix.Pebi, "Pi", $"(1024d * 1024 * 1024 * 1024 * 1024)"),
61-
new PrefixInfo(Prefix.Exbi, "Ei", $"(1024d * 1024 * 1024 * 1024 * 1024 * 1024)"),
43+
new PrefixInfo(Prefix.Kibi, "1024d", "Ki"),
44+
new PrefixInfo(Prefix.Mebi, "(1024d * 1024)", "Mi"),
45+
new PrefixInfo(Prefix.Gibi, "(1024d * 1024 * 1024)", "Gi"),
46+
new PrefixInfo(Prefix.Tebi, "(1024d * 1024 * 1024 * 1024)", "Ti"),
47+
new PrefixInfo(Prefix.Pebi, "(1024d * 1024 * 1024 * 1024 * 1024)", "Pi"),
48+
new PrefixInfo(Prefix.Exbi, "(1024d * 1024 * 1024 * 1024 * 1024 * 1024)", "Ei")
6249
}.ToDictionary(prefixInfo => prefixInfo.Prefix);
6350

64-
private PrefixInfo(Prefix prefix, string abbreviation, string factor)
51+
private PrefixInfo(Prefix prefix, string factor, string siPrefix, params (string cultureName, string prefix)[] cultureToPrefix)
6552
{
6653
Prefix = prefix;
67-
Abbreviation = abbreviation;
54+
SiPrefix = siPrefix;
55+
CultureToPrefix = cultureToPrefix;
6856
Factor = factor;
6957
}
58+
59+
/// <summary>
60+
/// The unit prefix.
61+
/// </summary>
62+
public Prefix Prefix { get; }
63+
64+
/// <summary>
65+
/// C# expression for the multiplier to prefix the conversion function.
66+
/// </summary>
67+
/// <example>Kilo has "1e3" in order to multiply by 1000.</example>
68+
public string Factor { get; }
69+
70+
/// <summary>
71+
/// The unit prefix abbreviation, such as "k" for kilo or "m" for milli.
72+
/// </summary>
73+
private string SiPrefix { get; }
74+
75+
/// <summary>
76+
/// Mapping from culture name to localized prefix abbreviation.
77+
/// </summary>
78+
private (string cultureName, string prefix)[] CultureToPrefix { get; }
79+
80+
/// <summary>
81+
/// Gets the localized prefix if configured, otherwise <see cref="SiPrefix" />.
82+
/// </summary>
83+
/// <param name="cultureName">Culture name, such as "en-US" or "ru-RU".</param>
84+
public string GetPrefixForCultureOrSiPrefix(string cultureName)
85+
{
86+
if (cultureName == null) throw new ArgumentNullException(nameof(cultureName));
87+
88+
var localizedPrefix = CultureToPrefix
89+
.Where(x => string.Equals(x.cultureName, cultureName, StringComparison.OrdinalIgnoreCase))
90+
.Select(x => x.prefix).FirstOrDefault();
91+
92+
return localizedPrefix ?? SiPrefix;
93+
}
7094
}
7195
}

Common/UnitDefinitions/Duration.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,7 @@
136136
},
137137
{
138138
"Culture": "ru-RU",
139-
"Abbreviations": [ "с", "сек" ],
140-
"AbbreviationsForPrefixes": {
141-
"Nano": ["нс", "нсек"],
142-
"Micro": ["мкс", "мксек"],
143-
"Milli": ["мс", "мсек"]
144-
}
139+
"Abbreviations": [ "с", "сек" ]
145140
}
146141
]
147142
}

Common/UnitDefinitions/Force.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@
7979
},
8080
{
8181
"Culture": "ru-RU",
82-
"Abbreviations": [ "Н" ],
83-
"AbbreviationsForPrefixes": { "Micro": "мкН", "Milli": "мН", "Deca": "даН", "Kilo": "кН", "Mega": "МН" }
82+
"Abbreviations": [ "Н" ]
8483
}
8584
]
8685
},

Common/UnitDefinitions/KinematicViscosity.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
},
3838
{
3939
"Culture": "ru-RU",
40-
"Abbreviations": [ "Ст" ],
41-
"AbbreviationsForPrefixes": { "Nano": "нСт", "Micro": "мкСт", "Milli": "мСт", "Centi": "сСт", "Deci": "дСт", "Kilo": "кСт" }
40+
"Abbreviations": [ "Ст" ]
4241
}
4342
]
4443
}

Common/UnitDefinitions/Length.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
},
2323
{
2424
"Culture": "ru-RU",
25-
"Abbreviations": [ "м" ],
26-
"AbbreviationsForPrefixes": { "Nano": "нм", "Micro": "мкм", "Milli": "мм", "Centi": "см", "Deci": "дм", "Hecto": "гм", "Kilo": "км" }
25+
"Abbreviations": [ "м" ]
2726
}
2827
]
2928
},

Common/UnitDefinitions/Mass.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
},
2323
{
2424
"Culture": "ru-RU",
25-
"Abbreviations": [ "г" ],
26-
"AbbreviationsForPrefixes": { "Nano": "нг", "Micro": "мкг", "Milli": "мг", "Centi": "сг", "Deci": "дг", "Deca": "даг", "Hecto": "гг", "Kilo": "кг" }
25+
"Abbreviations": [ "г" ]
2726
}
2827
]
2928
},
@@ -43,8 +42,7 @@
4342
},
4443
{
4544
"Culture": "ru-RU",
46-
"Abbreviations": [ "т" ],
47-
"AbbreviationsForPrefixes": { "Kilo": "кт", "Mega": "Мт" }
45+
"Abbreviations": [ "т" ]
4846
}
4947
]
5048
},

Common/UnitDefinitions/MassFlow.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@
9898
"Localization": [
9999
{
100100
"Culture": "en-US",
101-
"Abbreviations": [ "lb/d" ],
102-
"AbbreviationsForPrefixes": { "Mega": "Mlb/d" }
101+
"Abbreviations": [ "lb/d" ]
103102
}
104103
]
105104
},

Common/UnitDefinitions/MolarMass.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
},
2121
{
2222
"Culture": "ru-RU",
23-
"Abbreviations": [ "г/моль" ],
24-
"AbbreviationsForPrefixes": { "Nano": "нг/моль", "Micro": "мкг/моль", "Milli": "мг/моль", "Centi": "сг/моль", "Deci": "дг/моль", "Deca": "даг/моль", "Hecto": "гг/моль", "Kilo": "кг/моль" }
23+
"Abbreviations": [ "г/моль" ]
2524
}
2625
]
2726
},

0 commit comments

Comments
 (0)