i18n draft

This commit is contained in:
abdelkader
2025-06-02 21:37:39 -04:00
parent 1272d93ae9
commit dc47c59dc9
12 changed files with 612 additions and 18 deletions

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace vCardEditor
{
public interface ILocalizationProvider
{
string this[string key] { get; }
void SetLanguage(string langCode);
string CurrentLanguage { get; }
IReadOnlyDictionary<string, string> CurrentMessages { get; }
IEnumerable<string> AvailableLanguages { get; }
IEnumerable<string> AvailableLanguageNames { get; }
}
}

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using vCardEditor.Libs.TinyJson;
namespace vCardEditor
{
public class JsonLocalizationProvider : ILocalizationProvider
{
private readonly LocalizationFile _localization;
private string _currentLanguage;
public JsonLocalizationProvider(LocalizationFile localization, string defaultLanguage = "fr")
{
_localization = localization;
_currentLanguage = defaultLanguage;
}
public void SetLanguage(string langCode)
{
if (_localization.languages.ContainsKey(langCode))
_currentLanguage = langCode;
}
public string this[string key]
{
get
{
if (_localization.languages.TryGetValue(_currentLanguage, out var lang))
{
if (lang.messages.TryGetValue(key, out var value))
return value;
}
if (_localization.languages.TryGetValue("en", out var fallback))
{
if (lang.messages.TryGetValue(key, out var fallbackMsg))
return fallbackMsg;
}
return $"!{key}!";
}
}
public IReadOnlyDictionary<string, string> CurrentMessages =>
_localization.languages.TryGetValue(_currentLanguage, out var lang)
? lang.messages
: new Dictionary<string, string>();
public IEnumerable<string> AvailableLanguages => _localization.languages.Keys;
public IEnumerable<string> AvailableLanguageNames => _localization.languages?.Values != null
? _localization.languages.Values.Select(l => l?.name).Where(n => !string.IsNullOrEmpty(n))
: new List<string>();
public string CurrentLanguage => _currentLanguage;
}
}

View File

@@ -0,0 +1,377 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
namespace TinyJson
{
// Really simple JSON parser in ~300 lines
// - Attempts to parse JSON files with minimal GC allocation
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
// - Classes and structs can be parsed too!
// class Foo { public int Value; }
// "{\"Value\":10}".FromJson<Foo>()
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
// - No JIT Emit support to support AOT compilation on iOS
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
// - Only public fields and property setters on classes/structs will be written to
//
// Limitations:
// - No JIT Emit support to parse structures quickly
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
public static class JSONParser
{
[ThreadStatic] static Stack<List<string>> splitArrayPool;
[ThreadStatic] static StringBuilder stringBuilder;
[ThreadStatic] static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;
[ThreadStatic] static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;
public static T FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
if (propertyInfoCache == null) propertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
if (fieldInfoCache == null) fieldInfoCache = new Dictionary<Type, Dictionary<string, FieldInfo>>();
if (stringBuilder == null) stringBuilder = new StringBuilder();
if (splitArrayPool == null) splitArrayPool = new Stack<List<string>>();
//Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
for (int i = 0; i < json.Length; i++)
{
char c = json[i];
if (c == '"')
{
i = AppendUntilStringEnd(true, i, json);
continue;
}
if (char.IsWhiteSpace(c))
continue;
stringBuilder.Append(c);
}
//Parse the thing!
return (T)ParseValue(typeof(T), stringBuilder.ToString());
}
static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
{
stringBuilder.Append(json[startIdx]);
for (int i = startIdx + 1; i < json.Length; i++)
{
if (json[i] == '\\')
{
if (appendEscapeCharacter)
stringBuilder.Append(json[i]);
stringBuilder.Append(json[i + 1]);
i++;//Skip next character as it is escaped
}
else if (json[i] == '"')
{
stringBuilder.Append(json[i]);
return i;
}
else
stringBuilder.Append(json[i]);
}
return json.Length - 1;
}
//Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List<string>();
splitArray.Clear();
if (json.Length == 2)
return splitArray;
int parseDepth = 0;
stringBuilder.Length = 0;
for (int i = 1; i < json.Length - 1; i++)
{
switch (json[i])
{
case '[':
case '{':
parseDepth++;
break;
case ']':
case '}':
parseDepth--;
break;
case '"':
i = AppendUntilStringEnd(true, i, json);
continue;
case ',':
case ':':
if (parseDepth == 0)
{
splitArray.Add(stringBuilder.ToString());
stringBuilder.Length = 0;
continue;
}
break;
}
stringBuilder.Append(json[i]);
}
splitArray.Add(stringBuilder.ToString());
return splitArray;
}
internal static object ParseValue(Type type, string json)
{
if (type == typeof(string))
{
if (json.Length <= 2)
return string.Empty;
StringBuilder parseStringBuilder = new StringBuilder(json.Length);
for (int i = 1; i < json.Length - 1; ++i)
{
if (json[i] == '\\' && i + 1 < json.Length - 1)
{
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
if (j >= 0)
{
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
++i;
continue;
}
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
{
UInt32 c = 0;
if (UInt32.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out c))
{
parseStringBuilder.Append((char)c);
i += 5;
continue;
}
}
}
parseStringBuilder.Append(json[i]);
}
return parseStringBuilder.ToString();
}
if (type.IsPrimitive)
{
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
return result;
}
if (type == typeof(decimal))
{
decimal result;
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
return result;
}
if (type == typeof(DateTime))
{
DateTime result;
DateTime.TryParse(json.Replace("\"",""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result);
return result;
}
if (json == "null")
{
return null;
}
if (type.IsEnum)
{
if (json[0] == '"')
json = json.Substring(1, json.Length - 2);
try
{
return Enum.Parse(type, json, false);
}
catch
{
return 0;
}
}
if (type.IsArray)
{
Type arrayType = type.GetElementType();
if (json[0] != '[' || json[json.Length - 1] != ']')
return null;
List<string> elems = Split(json);
Array newArray = Array.CreateInstance(arrayType, elems.Count);
for (int i = 0; i < elems.Count; i++)
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
splitArrayPool.Push(elems);
return newArray;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
Type listType = type.GetGenericArguments()[0];
if (json[0] != '[' || json[json.Length - 1] != ']')
return null;
List<string> elems = Split(json);
var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
for (int i = 0; i < elems.Count; i++)
list.Add(ParseValue(listType, elems[i]));
splitArrayPool.Push(elems);
return list;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
Type keyType, valueType;
{
Type[] args = type.GetGenericArguments();
keyType = args[0];
valueType = args[1];
}
//Refuse to parse dictionary keys that aren't of type string
if (keyType != typeof(string))
return null;
//Must be a valid dictionary element
if (json[0] != '{' || json[json.Length - 1] != '}')
return null;
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
return null;
var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
continue;
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
object val = ParseValue(valueType, elems[i + 1]);
dictionary[keyValue] = val;
}
return dictionary;
}
if (type == typeof(object))
{
return ParseAnonymousValue(json);
}
if (json[0] == '{' && json[json.Length - 1] == '}')
{
return ParseObject(type, json);
}
return null;
}
static object ParseAnonymousValue(string json)
{
if (json.Length == 0)
return null;
if (json[0] == '{' && json[json.Length - 1] == '}')
{
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
return null;
var dict = new Dictionary<string, object>(elems.Count / 2);
for (int i = 0; i < elems.Count; i += 2)
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
return dict;
}
if (json[0] == '[' && json[json.Length - 1] == ']')
{
List<string> items = Split(json);
var finalList = new List<object>(items.Count);
for (int i = 0; i < items.Count; i++)
finalList.Add(ParseAnonymousValue(items[i]));
return finalList;
}
if (json[0] == '"' && json[json.Length - 1] == '"')
{
string str = json.Substring(1, json.Length - 2);
return str.Replace("\\", string.Empty);
}
if (char.IsDigit(json[0]) || json[0] == '-')
{
if (json.Contains("."))
{
double result;
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
return result;
}
else
{
int result;
int.TryParse(json, out result);
return result;
}
}
if (json == "true")
return true;
if (json == "false")
return false;
// handles json == "null" as well as invalid JSON
return null;
}
static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
{
Dictionary<string, T> nameToMember = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < members.Length; i++)
{
T member = members[i];
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
continue;
string name = member.Name;
if (member.IsDefined(typeof(DataMemberAttribute), true))
{
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
name = dataMemberAttribute.Name;
}
nameToMember.Add(name, member);
}
return nameToMember;
}
static object ParseObject(Type type, string json)
{
object instance = FormatterServices.GetUninitializedObject(type);
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
return instance;
Dictionary<string, FieldInfo> nameToField;
Dictionary<string, PropertyInfo> nameToProperty;
if (!fieldInfoCache.TryGetValue(type, out nameToField))
{
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
fieldInfoCache.Add(type, nameToField);
}
if (!propertyInfoCache.TryGetValue(type, out nameToProperty))
{
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
propertyInfoCache.Add(type, nameToProperty);
}
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
continue;
string key = elems[i].Substring(1, elems[i].Length - 2);
string value = elems[i + 1];
FieldInfo fieldInfo;
PropertyInfo propertyInfo;
if (nameToField.TryGetValue(key, out fieldInfo))
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
else if (nameToProperty.TryGetValue(key, out propertyInfo))
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
}
return instance;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace vCardEditor.Libs.TinyJson
{
public class LocalizationFile
{
public string version;
public Dictionary<string, LanguageData> languages = new Dictionary<string, LanguageData>();
}
public class LanguageData
{
public string name;
public Dictionary<string, string> messages = new Dictionary<string, string>();
}
}

View File

@@ -0,0 +1,44 @@
using System.IO;
using System.Reflection;
using TinyJson;
namespace vCardEditor.Libs.TinyJson
{
public static class LocalizationLoader
{
private const string EmbeddedResourceName = "vCardEditor.i18n.lang.json";
public static LocalizationFile LoadEmbedded()
{
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream(EmbeddedResourceName))
{
if (stream == null)
throw new FileNotFoundException($"Ressource embarquée '{EmbeddedResourceName}' introuvable.");
using (var reader = new StreamReader(stream))
{
var json = reader.ReadToEnd();
return Deserialize(json);
}
}
}
//public static LocalizationFile LoadFromFile(string filePath)
//{
// if (!File.Exists(filePath))
// return new LocalizationFile(); // fichier inexistant, retourne un fichier vide
// var json = File.ReadAllText(filePath);
// return Deserialize(json);
//}
private static LocalizationFile Deserialize(string json)
{
var result = JSONParser.FromJson<LocalizationFile>(json);
return result ?? new LocalizationFile();
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Thought.vCards;
using vCardEditor;
using vCardEditor.Model;
using vCardEditor.Repository;
using vCardEditor.View.Customs;
@@ -14,11 +15,14 @@ namespace VCFEditor.Presenter
{
private readonly IMainView _view;
private readonly IContactRepository _repository;
private readonly ILocalizationProvider _localization;
public MainPresenter(IMainView view, IContactRepository repository)
public MainPresenter(IMainView view, IContactRepository repository, ILocalizationProvider localization )
{
_view = view;
_repository = repository;
_localization = localization;
_view.LoadForm += LoadFormHandler;
_view.AddContact += AddContactHandler;
@@ -184,6 +188,8 @@ namespace VCFEditor.Presenter
private void LoadFormHandler(object sender, EventArg<FormState> e)
{
_view.LoadIntialState(ConfigRepository.Instance.FormState);
_view.LoadAvailablesLangs(_localization.AvailableLanguages);
_view.LoadLocalizedUI(_localization.CurrentMessages);
string[] paths = Environment.GetCommandLineArgs();
if (paths.Length > 1)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Windows.Forms;
using vCardEditor.Libs.TinyJson;
using vCardEditor.Repository;
using vCardEditor.View;
using VCFEditor.Presenter;
@@ -18,9 +19,12 @@ namespace vCardEditor
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var embedded = LocalizationLoader.LoadEmbedded();
var localizationProvider = new JsonLocalizationProvider(embedded);
FileHandler fileHandler = new FileHandler();
MainForm mainForm = new MainForm();
new MainPresenter(mainForm, new ContactRepository(fileHandler));
new MainPresenter(mainForm, new ContactRepository(fileHandler), localizationProvider);
Application.Run(mainForm);
}

View File

@@ -53,5 +53,7 @@ namespace VCFEditor.View
void ClearImageFromForm();
string DisplayOpenFolderDialog();
void LoadLocalizedUI(IReadOnlyDictionary<string, string> currentMessages);
void LoadAvailablesLangs(IEnumerable<string> availableLanguages);
}
}

View File

@@ -692,5 +692,18 @@ namespace vCardEditor.View
return result;
}
public void LoadLocalizedUI(IReadOnlyDictionary<string, string> currentMessages)
{
//this.fileToolStripMenuItem.Text = currentMessages["MSG_002"];
}
public void LoadAvailablesLangs(IEnumerable<string> availableLanguages)
{
foreach (var lang in availableLanguages)
{
}
}
}
}

View File

@@ -0,0 +1,19 @@
{
"version": "1.0",
"languages": {
"en": {
"name": "English",
"messages": {
"MSG_001": "Save current file before?",
"MSG_002": "File"
}
},
"fr": {
"name": "Français",
"messages": {
"MSG_001": "Sauvegarder le fichier en cours",
"MSG_002": "Fichier"
}
}
}
}

View File

@@ -57,6 +57,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
@@ -66,6 +67,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ILocalizationProvider.cs" />
<Compile Include="JsonLocalizationProvider.cs" />
<Compile Include="Libs\QRCoder\AbstractQRCode.cs" />
<Compile Include="Libs\QRCoder\ArtQRCode.cs" />
<Compile Include="Libs\QRCoder\ASCIIQRCode.cs" />
@@ -83,6 +86,9 @@
<Compile Include="Libs\QRCoder\QRCodeData.cs" />
<Compile Include="Libs\QRCoder\QRCodeGenerator.cs" />
<Compile Include="Libs\QRCoder\SvgQRCode.cs" />
<Compile Include="Libs\TinyJson\JSONParser.cs" />
<Compile Include="Libs\TinyJson\LocalizationFile.cs" />
<Compile Include="Libs\TinyJson\LocalizationLoader.cs" />
<Compile Include="Model\vCardPropeties.cs" />
<Compile Include="Model\Column.cs" />
<Compile Include="Model\FixedList.cs" />
@@ -263,6 +269,7 @@
<DependentUpon>QRDialog.cs</DependentUpon>
</EmbeddedResource>
<None Include="app.config" />
<EmbeddedResource Include="i18n\lang.json" />
<None Include="Libs\QRCoder\Assets\nuget-readme.md" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>

View File

@@ -10,6 +10,7 @@ using System;
using AutoFixture;
using Thought.vCards;
using System.Collections.Generic;
using vCardEditor;
namespace vCardEditor_Test
{
@@ -24,7 +25,8 @@ namespace vCardEditor_Test
fileHandler.ReadAllLines(Arg.Any<string>()).Returns(Entries.vcfOneEntry);
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("filename.aaa"));
view.Received().DisplayMessage(Arg.Any<string>(), Arg.Any<string>());
@@ -42,8 +44,10 @@ namespace vCardEditor_Test
repo.GetExtension(Arg.Any<string>()).Returns(".vcf");
var view = Substitute.For<IMainView>();
var presenter = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("filename.vcf"));
view.Received().DisplayContacts(Arg.Is<SortableBindingList<Contact>>(x=>x.Count == 1));
@@ -64,7 +68,9 @@ namespace vCardEditor_Test
var view = Substitute.For<IMainView>();
view.AskMessage(Arg.Any<string>(), Arg.Any<string>()).Returns(true);
var presenter = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("filename.vcf"));
repo.Contacts[1].isDirty = true;
@@ -84,7 +90,9 @@ namespace vCardEditor_Test
var view = Substitute.For<IMainView>();
var presenter = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("aaa.vcf"));
view.SaveContactsSelected += Raise.Event();
@@ -105,7 +113,9 @@ namespace vCardEditor_Test
var view = Substitute.For<IMainView>();
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("aaa.vcf"));
view.SaveContactsSelected += Raise.Event();
@@ -123,8 +133,10 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
repo.GetExtension(Arg.Any<string>()).Returns(".vcf");
var view = Substitute.For<IMainView>();
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.NewFileOpened += Raise.EventWith(new EventArg<string>("aaa.vcf"));
//Mouse click on second row.
@@ -148,7 +160,10 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
repo.LoadContacts("aaa.vcf");
view.CopyTextToClipboardEvent += Raise.Event();
@@ -167,7 +182,9 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
var contact = repo.LoadContacts("aaa.vcf");
view.AddressRemoved += Raise.EventWith(new EventArg<int>(0));
@@ -186,7 +203,9 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
var contact = repo.LoadContacts("aaa.vcf");
var fixture = new Fixture { RepeatCount = 2 };
@@ -210,7 +229,9 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
var contact = repo.LoadContacts("aaa.vcf");
var fixture = new Fixture { RepeatCount = 2 };
@@ -232,7 +253,8 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
_ = repo.LoadContacts("aaa.vcf");
view.ExportImage += Raise.Event();
@@ -250,7 +272,8 @@ namespace vCardEditor_Test
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
view.SelectedContactIndex.Returns(0);
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
var contact = repo.LoadContacts("aaa.vcf");
view.ModifyImage += Raise.EventWith(new EventArg<string>(""));
@@ -266,7 +289,8 @@ namespace vCardEditor_Test
var fileHandler = Substitute.For<IFileHandler>();
var repo = Substitute.For<ContactRepository>(fileHandler);
var view = Substitute.For<IMainView>();
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.AddContact += Raise.Event();
view.Received().DisplayContacts(Arg.Any<SortableBindingList<Contact>>());
@@ -288,7 +312,8 @@ namespace vCardEditor_Test
var view = Substitute.For<IMainView>();
view.DisplayOpenFolderDialog().Returns("aaa");
_ = new MainPresenter(view, repo);
var localization = Substitute.For<ILocalizationProvider>();
_ = new MainPresenter(view, repo, localization);
view.SplitFileEvent += Raise.Event();
//Should save only 3 files.