mirror of
https://github.com/abdelkader/vCardEditor
synced 2025-12-12 08:27:19 +07:00
i18n draft
This commit is contained in:
22
vCardEditor/ILocalizationProvider.cs
Normal file
22
vCardEditor/ILocalizationProvider.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
59
vCardEditor/JsonLocalizationProvider.cs
Normal file
59
vCardEditor/JsonLocalizationProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
377
vCardEditor/Libs/TinyJson/JSONParser.cs
Normal file
377
vCardEditor/Libs/TinyJson/JSONParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vCardEditor/Libs/TinyJson/LocalizationFile.cs
Normal file
16
vCardEditor/Libs/TinyJson/LocalizationFile.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
44
vCardEditor/Libs/TinyJson/LocalizationLoader.cs
Normal file
44
vCardEditor/Libs/TinyJson/LocalizationLoader.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -53,5 +53,7 @@ namespace VCFEditor.View
|
||||
|
||||
void ClearImageFromForm();
|
||||
string DisplayOpenFolderDialog();
|
||||
void LoadLocalizedUI(IReadOnlyDictionary<string, string> currentMessages);
|
||||
void LoadAvailablesLangs(IEnumerable<string> availableLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
vCardEditor/i18n/lang.json
Normal file
19
vCardEditor/i18n/lang.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user