diff --git a/Linguini.Bench/Linguini.Bench.csproj b/Linguini.Bench/Linguini.Bench.csproj
index 471aca5..bfe1545 100644
--- a/Linguini.Bench/Linguini.Bench.csproj
+++ b/Linguini.Bench/Linguini.Bench.csproj
@@ -3,7 +3,7 @@
Exe
false
- net6.0
+ net6.0;net8.0
diff --git a/Linguini.Bundle.Test/Linguini.Bundle.Test.csproj b/Linguini.Bundle.Test/Linguini.Bundle.Test.csproj
index 2caf3f5..8ad1b66 100644
--- a/Linguini.Bundle.Test/Linguini.Bundle.Test.csproj
+++ b/Linguini.Bundle.Test/Linguini.Bundle.Test.csproj
@@ -5,13 +5,13 @@
enable
Library
0.7.0
- net6.0
+ net6.0;net8.0
-
+
diff --git a/Linguini.Bundle.Test/Unit/BundleTests.cs b/Linguini.Bundle.Test/Unit/BundleTests.cs
index f8917b7..401ea50 100644
--- a/Linguini.Bundle.Test/Unit/BundleTests.cs
+++ b/Linguini.Bundle.Test/Unit/BundleTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
@@ -22,23 +23,23 @@ public class BundleTests
private readonly Func _formatter = _ => "";
private readonly Func _transform = str => str.ToUpper(CultureInfo.InvariantCulture);
- private static string _res1 = @"
+ private const string Res1 = @"
term = term
.attr = 3";
- private static string _wrong = @"
+ private const string Wrong = @"
term = 1";
- private static string _multi = @"
+ private const string Multi = @"
term1 = val1
term2 = val2
.attr = 6";
- private static string _replace1 = @"
+ private const string Replace1 = @"
term1 = val1
term2 = val2";
- private static string _replace2 = @"
+ private const string Replace2 = @"
term1 = xxx
new1 = new
.attr = 6";
@@ -90,7 +91,7 @@ public void TestReplaceMessage()
{
var bundler = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
- .AddResource(_replace1);
+ .AddResource(Replace1);
var bundle = bundler.UncheckedBuild();
Assert.That(bundle.TryGetAttrMessage("term1", null, out _, out var termMsg));
@@ -98,7 +99,7 @@ public void TestReplaceMessage()
Assert.That(bundle.TryGetAttrMessage("term2", null, out _, out var termMsg2));
Assert.That("val2", Is.EqualTo(termMsg2));
- bundle.AddResourceOverriding(_replace2);
+ bundle.AddResourceOverriding(Replace2);
Assert.That(bundle.TryGetAttrMessage("term2", null, out _, out _));
Assert.That(bundle.TryGetAttrMessage("term1", null, out _, out termMsg));
Assert.That("xxx", Is.EqualTo(termMsg));
@@ -112,7 +113,7 @@ public void TestExceptions()
{
var bundler = LinguiniBuilder.Builder()
.Locales("en-US", "sr-RS")
- .AddResources(_wrong, _res1)
+ .AddResources(Wrong, Res1)
.SetFormatterFunc(_formatter)
.SetTransformFunc(_transform)
.AddFunction("id", _idFunc)
@@ -140,7 +141,7 @@ public void TestEnumeration()
{
var bundler = LinguiniBuilder.Builder()
.Locale("en-US")
- .AddResource(_multi)
+ .AddResource(Multi)
.AddFunction("id", _idFunc)
.AddFunction("zero", _zeroFunc)
.UncheckedBuild();
@@ -176,11 +177,16 @@ public void TestConcurrencyOption()
UseConcurrent = true,
};
var optBundle = FluentBundle.MakeUnchecked(bundleOpt);
+
Parallel.For(0, 10, i => optBundle.AddResource($"term-1 = {i}", out _));
Parallel.For(0, 10, i => optBundle.AddResource($"term-2= {i}", out _));
Parallel.For(0, 10, i => optBundle.TryGetAttrMessage("term-1", null, out _, out _));
Parallel.For(0, 10, i => optBundle.AddResourceOverriding($"term-2= {i + 1}"));
Assert.That(optBundle.HasMessage("term-1"));
+
+ // Frozen bundle are read only and should be thread-safe
+ var frozenBundle = optBundle.ToFrozenBundle();
+ Parallel.For(0, 10, i => frozenBundle.TryGetAttrMessage("term-1", null, out _, out _));
}
[Test]
@@ -208,7 +214,7 @@ public void TestFuncAddBehavior()
bundle.TryAddFunction("id", _idFunc);
Assert.That(bundle.TryAddFunction("id", _zeroFunc), Is.False);
- Assert.Throws(() => bundle.AddFunctionUnchecked("id", _zeroFunc));
+ Assert.Throws(() => bundle.AddFunctionUnchecked("id", _zeroFunc));
}
[Test]
@@ -221,7 +227,7 @@ public void TestBehavior(string idWithAttr, bool found)
{
var bundle = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
- .AddResource(_replace2)
+ .AddResource(Replace2)
.UncheckedBuild();
Assert.That(bundle.TryGetAttrMessage(idWithAttr, null, out _, out _),
@@ -238,7 +244,7 @@ public void TestHasAttrMessage(string idWithAttr, bool found)
{
var bundle = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo("en"))
- .AddResource(_replace2)
+ .AddResource(Replace2)
.UncheckedBuild();
Assert.That(bundle.TryGetAttrMessage(idWithAttr, null, out _, out _),
@@ -252,7 +258,7 @@ public static IEnumerable TestBundleErrors
yield return new TestCaseData("### Comment\r\nterm1")
.Returns(new List
{
- new ErrorSpan(2, 13, 18, 18, 19)
+ new(2, 13, 18, 18, 19)
});
}
}
@@ -264,6 +270,8 @@ public static IEnumerable TestBundleErrors
var (_, error) = LinguiniBuilder.Builder().Locale("en-US")
.AddResource(input)
.Build();
+ Debug.Assert(error != null, nameof(error) + " != null");
+ Assert.That(error, Is.Not.Empty);
return error.Select(e => e.GetSpan()).ToList();
}
@@ -287,7 +295,7 @@ public void TestDynamicReference(string input)
var (bundle, err) = LinguiniBuilder.Builder(useExperimental: true).Locale("en-US")
.AddResource(input)
.Build();
- Assert.That(err, Is.Empty);
+ Assert.That(err, Is.Null);
var args = new Dictionary()
{
["attacker"] = (FluentReference)"cat",
@@ -312,18 +320,23 @@ [neuter] It
[Test]
[Parallelizable]
- public void TestMacrosFail()
+ public void TestExtensionsWork()
{
var (bundle, err) = LinguiniBuilder.Builder(useExperimental: true).Locale("en-US")
.AddResource(Macros)
.Build();
- Assert.That(err, Is.Empty);
+ Assert.That(err, Is.Null);
var args = new Dictionary
{
["style"] = (FluentString)"chicago",
};
Assert.That(bundle.TryGetMessage("call-attr-no-args", args, out _, out var message));
Assert.That("It", Is.EqualTo(message));
+
+ // Check Frozen bundle behaves similarly
+ var frozenBundle = bundle.ToFrozenBundle();
+ Assert.That(frozenBundle.TryGetMessage("call-attr-no-args", args, out _, out var frozenMessage));
+ Assert.That("It", Is.EqualTo(frozenMessage));
}
private const string DynamicSelectors = @"
-creature-fairy = fairy
@@ -344,7 +357,7 @@ public void TestDynamicSelectors()
.Locale("en-US")
.AddResource(DynamicSelectors)
.Build();
- Assert.That(err, Is.Empty);
+ Assert.That(err, Is.Null);
var args = new Dictionary
{
["object"] = (FluentReference)"creature-elf",
@@ -357,6 +370,56 @@ public void TestDynamicSelectors()
};
Assert.That(bundle.TryGetMessage("you-see", args, out _, out var message2));
Assert.That("You see a fairy.", Is.EqualTo(message2));
+
+ // Check Frozen bundle behaves similarly
+ var frozenBundle = bundle.ToFrozenBundle();
+ args = new Dictionary
+ {
+ ["object"] = (FluentReference)"creature-elf",
+ };
+ Assert.That(frozenBundle.TryGetMessage("you-see", args, out _, out var frozenMessage1));
+ Assert.That("You see an elf.", Is.EqualTo(frozenMessage1));
+ args = new Dictionary
+ {
+ ["object"] = (FluentReference)"creature-fairy",
+ };
+ Assert.That(frozenBundle.TryGetMessage("you-see", args, out _, out var frozenMessage2));
+ Assert.That("You see a fairy.", Is.EqualTo(frozenMessage2));
+ }
+
+ [Test]
+ public void TestDeepClone()
+ {
+ var originalBundleOption = new FluentBundleOption
+ {
+ Locales = { "en-US" },
+ MaxPlaceable = 123,
+ UseIsolating = false,
+ TransformFunc = _transform,
+ FormatterFunc = _formatter,
+ Functions = new Dictionary()
+ {
+ ["zero"] = _zeroFunc,
+ ["id"] = _idFunc,
+ }
+ };
+
+ // Assume FluentBundle object has DeepClone method
+ FluentBundle originalBundle = FluentBundle.MakeUnchecked(originalBundleOption);
+ FluentBundle clonedBundle = originalBundle.DeepClone();
+
+ // Assert that the original and cloned objects are not the same reference
+ Assert.That(originalBundle, Is.Not.SameAs(clonedBundle));
+
+ // Assert that the properties are copied properly
+ Assert.That(originalBundle, Is.EqualTo(clonedBundle));
+
+ // Assert that if original property is changed, new property isn't.
+ originalBundle.AddFunctionOverriding("zero", _idFunc);
+ clonedBundle.TryGetFunction("zero", out var clonedZero);
+ Assert.That((FluentFunction) _zeroFunc, Is.EqualTo(clonedZero));
+ originalBundle.TryGetFunction("zero", out var originalZero);
+ Assert.That((FluentFunction) _idFunc, Is.EqualTo(originalZero));
}
}
}
\ No newline at end of file
diff --git a/Linguini.Bundle.Test/Yaml/YamlSuiteParser.cs b/Linguini.Bundle.Test/Yaml/YamlSuiteParser.cs
index 4b141b5..4b06ca4 100644
--- a/Linguini.Bundle.Test/Yaml/YamlSuiteParser.cs
+++ b/Linguini.Bundle.Test/Yaml/YamlSuiteParser.cs
@@ -3,7 +3,7 @@
using System.IO;
using Linguini.Bundle.Builder;
using Linguini.Bundle.Errors;
-using Linguini.Bundle.Func;
+using Linguini.Bundle.Function;
using Linguini.Bundle.Types;
using NUnit.Framework;
using YamlDotNet.RepresentationModel;
@@ -88,7 +88,10 @@ public void YamlTestSuiteMethod(ResolverTestSuite parsedTestSuite, LinguiniBuild
foreach (var res in parsedTestSuite.Resources)
{
bundle.AddResource(res, out var err);
- errors.AddRange(err);
+ if (err != null)
+ {
+ errors.AddRange(err);
+ }
}
if (parsedTestSuite.Bundle != null)
@@ -147,7 +150,10 @@ public void YamlTestSuiteMethod(ResolverTestSuite parsedTestSuite, LinguiniBuild
else
{
testBundle.AddResource(res, out var errs);
- errors.AddRange(errs);
+ if (errs != null)
+ {
+ errors.AddRange(errs);
+ }
}
}
}
@@ -189,10 +195,10 @@ private static string GetFullPathFor(string file)
private static void AssertErrorCases(List expectedErrors,
- IList errs,
+ IList? errs,
String testName)
{
- Assert.That(expectedErrors.Count, Is.EqualTo(errs.Count), testName);
+ Assert.That(expectedErrors.Count, Is.EqualTo(errs!.Count), testName);
for (var i = 0; i < expectedErrors.Count; i++)
{
var actualError = errs[i];
diff --git a/Linguini.Bundle/Builder/FluentBundleOption.cs b/Linguini.Bundle/Builder/FluentBundleOption.cs
index c0aff74..03fa4cb 100644
--- a/Linguini.Bundle/Builder/FluentBundleOption.cs
+++ b/Linguini.Bundle/Builder/FluentBundleOption.cs
@@ -12,7 +12,7 @@ public class FluentBundleOption
public bool UseIsolating { get; init; } = true;
public byte MaxPlaceable { get; init; } = 100;
- public IList Locales { get; init; } = new List();
+ public List Locales { get; init; } = new List();
public IDictionary Functions { get; init; } =
new Dictionary();
diff --git a/Linguini.Bundle/Builder/LinguiniBuilder.cs b/Linguini.Bundle/Builder/LinguiniBuilder.cs
index 6613f5b..a326402 100644
--- a/Linguini.Bundle/Builder/LinguiniBuilder.cs
+++ b/Linguini.Bundle/Builder/LinguiniBuilder.cs
@@ -48,7 +48,7 @@ public interface IBuildStep : IStep
{
FluentBundle UncheckedBuild();
- (FluentBundle, List) Build();
+ (FluentBundle, List?) Build();
}
public interface IReadyStep : IBuildStep
@@ -65,7 +65,6 @@ private class StepBuilder : IReadyStep, ILocaleStep, IResourceStep
private CultureInfo _culture;
private readonly List _locales = new();
private readonly List _resources = new();
- private readonly List