Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Jint/Jint.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@
<None Include="../README.md" Pack="true" PackagePath="\"/>
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Native/Intl/Data/CompactPatterns.txt"/>
<EmbeddedResource Include="Native/Intl/Data/LikelySubtags.txt"/>
<EmbeddedResource Include="Native/Intl/Data/ListPatternsData.txt"/>
<EmbeddedResource Include="Native/Intl/Data/LocaleData.txt"/>
<EmbeddedResource Include="Native/Intl/Data/RelativeTimePatternsData.txt"/>
<EmbeddedResource Include="Native/Intl/Data/UnitPatternsData.txt"/>
<EmbeddedResource Include="Native/Intl/Data/WeekData.txt"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Acornima" />
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" />
Expand Down
229 changes: 229 additions & 0 deletions Jint/Native/Intl/Data/CompactPatterns.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
using System.IO;

namespace Jint.Native.Intl.Data;

/// <summary>
/// Provides CLDR compact number patterns for locale-aware compact notation.
/// Data is loaded from embedded CompactPatterns.txt resource.
/// </summary>
internal static class CompactPatterns
{
private static readonly object _lock = new object();
private static Dictionary<string, LocaleCompactData>? _patterns;
private static volatile bool _loaded;

/// <summary>
/// Gets compact patterns for a language code.
/// Falls back to English if not found.
/// </summary>
public static LocaleCompactData GetPatterns(string? language)
{
EnsureLoaded();

if (!string.IsNullOrEmpty(language))
{
// Try exact match first
if (_patterns!.TryGetValue(language!, out var data))
{
return data;
}

// Handle Chinese script variants
// zh-TW, zh-Hant-TW, zh-Hant, zh-HK, zh-MO → Traditional Chinese (zh-TW patterns)
// zh-CN, zh-Hans-CN, zh-Hans, zh-SG → Simplified Chinese (zh patterns)
if (language!.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
{
var isTraditional = language.Contains("-TW", StringComparison.OrdinalIgnoreCase) ||
language.Contains("-HK", StringComparison.OrdinalIgnoreCase) ||
language.Contains("-MO", StringComparison.OrdinalIgnoreCase) ||
language.Contains("-Hant", StringComparison.OrdinalIgnoreCase);
var zhKey = isTraditional ? "zh-TW" : "zh";
if (_patterns.TryGetValue(zhKey, out data))
{
return data;
}
}

// Try base language (e.g., "de" from "de-DE")
var dashIndex = language.IndexOf('-');
if (dashIndex > 0)
{
var baseLang = language.Substring(0, dashIndex);
if (_patterns.TryGetValue(baseLang, out data))
{
return data;
}
}
}

// Fall back to English
return _patterns!.TryGetValue("en", out var enData) ? enData : LocaleCompactData.Default;
}

private static void EnsureLoaded()
{
if (_loaded)
{
return;
}

lock (_lock)
{
if (_loaded)
{
return;
}

_patterns = new Dictionary<string, LocaleCompactData>(StringComparer.OrdinalIgnoreCase);

var assembly = typeof(CompactPatterns).Assembly;
using var stream = assembly.GetManifestResourceStream("Jint.Native.Intl.Data.CompactPatterns.txt");
if (stream is null)
{
_loaded = true;
return;
}

using var reader = new StreamReader(stream);
string? currentLang = null;
LocaleCompactData? currentData = null;

while (reader.ReadLine() is { } line)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}

if (line.Length > 2 && line[0] == '[' && line[line.Length - 1] == ']')
{
// Save previous locale data
if (currentLang != null && currentData != null)
{
_patterns[currentLang] = currentData;
}

currentLang = line.Substring(1, line.Length - 2);
currentData = new LocaleCompactData();
continue;
}

if (currentData == null)
{
continue;
}

var eqIndex = line.IndexOf('=');
if (eqIndex < 0)
{
continue;
}

var key = line.Substring(0, eqIndex);
var value = line.Substring(eqIndex + 1);

switch (key)
{
case "short_thousand":
currentData.ShortThousand = value;
break;
case "short_million":
currentData.ShortMillion = value;
break;
case "short_billion":
currentData.ShortBillion = value;
break;
case "short_trillion":
currentData.ShortTrillion = value;
break;
case "long_thousand":
currentData.LongThousand = value;
break;
case "long_million":
currentData.LongMillion = value;
break;
case "long_billion":
currentData.LongBillion = value;
break;
case "long_trillion":
currentData.LongTrillion = value;
break;
case "threshold":
if (long.TryParse(value, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var threshold))
{
currentData.Threshold = threshold;
}
break;
case "threshold_long":
if (long.TryParse(value, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var thresholdLong))
{
currentData.ThresholdLong = thresholdLong;
}
break;
case "divisor_million":
if (long.TryParse(value, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var divMillion))
{
currentData.DivisorMillion = divMillion;
}
break;
case "divisor_billion":
if (long.TryParse(value, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var divBillion))
{
currentData.DivisorBillion = divBillion;
}
break;
case "short_space":
currentData.ShortSpace = !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
break;
case "long_space":
currentData.LongSpace = !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
break;
}
}

// Save last locale data
if (currentLang != null && currentData != null)
{
_patterns[currentLang] = currentData;
}

_loaded = true;
}
}

internal sealed class LocaleCompactData
{
public string ShortThousand { get; set; } = "K";
public string ShortMillion { get; set; } = "M";
public string ShortBillion { get; set; } = "B";
public string ShortTrillion { get; set; } = "T";
public string LongThousand { get; set; } = "thousand";
public string LongMillion { get; set; } = "million";
public string LongBillion { get; set; } = "billion";
public string LongTrillion { get; set; } = "trillion";
public long Threshold { get; set; } = 1000;
public long? ThresholdLong { get; set; }
public long DivisorMillion { get; set; } = 1_000_000;
public long DivisorBillion { get; set; } = 1_000_000_000;
public bool ShortSpace { get; set; } = true; // Default to true (has space)
public bool LongSpace { get; set; } = true; // Default to true (has space)

/// <summary>
/// Gets the threshold to use based on display mode.
/// </summary>
public long GetThreshold(bool isLong)
{
if (isLong && ThresholdLong.HasValue)
{
return ThresholdLong.Value;
}
return Threshold;
}

public static LocaleCompactData Default { get; } = new LocaleCompactData();
}
}
149 changes: 149 additions & 0 deletions Jint/Native/Intl/Data/CompactPatterns.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
[en]
short_thousand=K
short_million=M
short_billion=B
short_trillion=T
long_thousand=thousand
long_million=million
long_billion=billion
long_trillion=trillion
threshold=1000
short_space=false

[de]
short_thousand=
short_million=Mio.
short_billion=Mrd.
short_trillion=Bio.
long_thousand=Tausend
long_million=Millionen
long_billion=Milliarden
long_trillion=Billionen
threshold=1000000
threshold_long=1000

[ja]
short_thousand=
short_million=万
short_billion=億
short_trillion=兆
long_thousand=
long_million=万
long_billion=億
long_trillion=兆
threshold=10000
divisor_million=10000
divisor_billion=100000000
short_space=false
long_space=false

[ko]
short_thousand=천
short_million=만
short_billion=억
short_trillion=조
long_thousand=천
long_million=만
long_billion=억
long_trillion=조
threshold=1000
divisor_million=10000
divisor_billion=100000000
short_space=false
long_space=false

[zh]
short_thousand=
short_million=万
short_billion=亿
short_trillion=兆
long_thousand=
long_million=万
long_billion=亿
long_trillion=兆
threshold=10000
divisor_million=10000
divisor_billion=100000000
short_space=false
long_space=false

[zh-TW]
short_thousand=
short_million=萬
short_billion=億
short_trillion=兆
long_thousand=
long_million=萬
long_billion=億
long_trillion=兆
threshold=10000
divisor_million=10000
divisor_billion=100000000
short_space=false
long_space=false

[fr]
short_thousand=k
short_million=M
short_billion=Md
short_trillion=Bn
long_thousand=millier
long_million=millions
long_billion=milliards
long_trillion=billions
threshold=1000

[es]
short_thousand=k
short_million=M
short_billion=mil M
short_trillion=B
long_thousand=mil
long_million=millones
long_billion=mil millones
long_trillion=billones
threshold=1000

[it]
short_thousand=
short_million=Mln
short_billion=Mrd
short_trillion=Bln
long_thousand=mila
long_million=milioni
long_billion=miliardi
long_trillion=bilioni
threshold=1000000

[pt]
short_thousand=mil
short_million=mi
short_billion=bi
short_trillion=tri
long_thousand=mil
long_million=milhões
long_billion=bilhões
long_trillion=trilhões
threshold=1000

[ru]
short_thousand=тыс.
short_million=млн
short_billion=млрд
short_trillion=трлн
long_thousand=тысяч
long_million=миллионов
long_billion=миллиардов
long_trillion=триллионов
threshold=1000

[ar]
short_thousand=ألف
short_million=مليون
short_billion=مليار
short_trillion=ترليون
long_thousand=ألف
long_million=مليون
long_billion=مليار
long_trillion=ترليون
threshold=1000
Loading