Search code examples
c#listtranslationkey-valueglobalization

How to get ListDictionary from List with KeyValuePair to translate some string?


I need translation for programmatically changed label text. Therefor I built a List with translations. The list conpect looks like this:

translation["de"]["label1"] = "german text";
translation["en"]["label1"] = "english text";

Here is my actual code to build the list:

public List<KeyValuePair<string, ListDictionary>> translations = new List<KeyValuePair<string, ListDictionary>>();
ListDictionary tDE = new ListDictionary();
ListDictionary tEN = new ListDictionary();
tDE.Add("label1", "german text");
tEN.Add("label1", "english text");
translations.Add(new KeyValuePair<string, ListDictionary>("de", tDE));
translations.Add(new KeyValuePair<string, ListDictionary>("en", tEN));

How can I now get a value of a translation?

My approach is like this:

public string getLocStr(string lang, string key)
{
     string str = "";
     foreach (var trans in translations)
     {
           // how to get a List<string,string> to retrieve the $value from by $key?
           // eg: str = trans[$lang][$key]
     }
     return str;
}

Solution

  • Having such an enum:

    public enum Language
    {
      None,
      EN,
      FR
    }
    

    Using:

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Text;
    using EnumsNET;
    

    And having that dictionary:

    [Serializable]
    class TranslationsDictionary : NullSafeOfStringDictionary<Language>
    {
      public TranslationsDictionary() : base()
      {
      }
      protected TranslationsDictionary(SerializationInfo info, StreamingContext context) : base(info, context)
      {
      }
    }
    

    From this one:

    [Serializable]
    public class NullSafeOfStringDictionary<T> : Dictionary<T, string>
    {
    
      public NullSafeOfStringDictionary()
      {
      }
    
      public NullSafeOfStringDictionary(int capacity) : base(capacity)
      {
      }
    
      public NullSafeOfStringDictionary(IEqualityComparer<T> comparer) : base(comparer)
      {
      }
    
      public NullSafeOfStringDictionary(IDictionary<T, string> dictionary) : base(dictionary)
      {
      }
    
      public NullSafeOfStringDictionary(int capacity, IEqualityComparer<T> comparer) : base(capacity, comparer)
      {
      }
    
      public NullSafeOfStringDictionary(IDictionary<T, string> dictionary, IEqualityComparer<T> comparer) : base(dictionary, comparer)
      {
      }
    
      protected NullSafeOfStringDictionary(SerializationInfo info, StreamingContext context) : base(info, context)
      {
      }
    
      public new string this[T key]
      {
        get
        {
          return ContainsKey(key) ? base[key] : null;
        }
        set
        {
          if ( ContainsKey(key) )
            base[key] = value;
          else
            Add(key, value);
        }
      }
    }
    

    You can use this class:

    static class Localizer
    {
    
      private const string ERR = "<Not translated>";
    
      /// <summary>
      /// Get the string translation.
      /// </summary>
      /// <param name="values">The dictionary containing lang>translation.</param>
      static public string GetLang(this TranslationsDictionary values)
      {
        return values?[Languages.Current] ?? values?[Languages.Default] ?? ERR;
      }
    
      /// <summary>
      /// Get the string translation.
      /// </summary>
      /// <param name="values">The dictionary containing lang>translation.</param>
      /// <param name="parameters">Parameters for the translated string.</param>
      static public string GetLang(this TranslationsDictionary values, params object[] parameters)
      {
        return string.Format(values?.GetLang(), parameters) ?? ERR + " " + string.Join(",", parameters);
      }
    
      /// <summary>
      /// Get the string translation.
      /// </summary>
      /// <typeparam name="T">The type.</typeparam>
      /// <param name="values">The dictionary containing value>lang>translation.</param>
      /// <param name="value">The value to translate.</param>
      /// <param name="forceEnglish">True to force get in english.</param>
      static public string GetLang<T>(this NullSafeDictionary<T, TranslationsDictionary> values, T value, bool forceEnglish = false)
      {
        var lang = forceEnglish ? Language.EN : Languages.Current;
        return values?[value]?[lang] ?? values?[value]?[Languages.Default] ?? ERR;
      }
    
      /// <summary>
      /// Get the list translation.
      /// </summary>
      /// <param name="values">The dictionary containing lang>list.</param>
      static public NullSafeStringList GetLang(this NullSafeDictionary<Language, NullSafeStringList> values)
      {
        return values?[Languages.Current] ?? values?[Languages.Default] ?? new NullSafeStringList();
      }
    
      /// <summary>
      /// Get the list translation.
      /// </summary>
      /// <typeparam name="T">The type.</typeparam>
      /// <param name="values">The dictionary containing lang>list.</param>
      static public NullSafeList<T> GetLang<T>(this NullSafeDictionary<Language, NullSafeList<T>> values)
        where T : class
      {
        return values?[Languages.Current] ?? values?[Languages.Default] ?? new NullSafeList<T>();
      }
    
      /// <summary>
      /// Get the string list translation.
      /// </summary>
      /// <param name="values">The dictionary containing lang>translations.</param>
      static public string[] GetLang(this NullSafeDictionary<Language, string[]> values)
      {
        return values?[Languages.Current] ?? values?[Languages.Default] ?? new string[1] { ERR };
      }
    
      /// <summary>
      /// Get the string translation.
      /// </summary>
      /// <typeparam name="T">The type.</typeparam>
      /// <param name="values">The dictionary containing lang>value>translation.</param>
      /// <param name="value">The value to translate.</param>
      static public string GetLang<T>(this NullSafeDictionary<Language, NullSafeOfStringDictionary<T>> values, T value)
        where T : Enum
      {
        return values?[Languages.Current]?[value] ?? values?[Languages.Default]?[value] ?? ERR;
      }
    
      /// <summary>
      /// Remove diacritics signs.
      /// </summary>
      public static string RemoveDiacritics(this string str)
      {
        if ( str.IsNullOrEmpty() ) return str;
        var normalized = str.Normalize(NormalizationForm.FormD);
        var builder = new StringBuilder();
        foreach ( var c in normalized )
          if ( CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark )
            builder.Append(c);
        return builder.ToString().Normalize(NormalizationForm.FormC);
      }
    
    }
    

    Having also:

    [Serializable]
    public class NullSafeList<T> : List<T>
      where T : class
    {
    
      public NullSafeList()
      {
      }
    
      public NullSafeList(int capacity) : base(capacity)
      {
      }
    
      public NullSafeList(IEnumerable<T> collection) : base(collection)
      {
      }
    
      public new T this[int index]
      {
        get
        {
          CheckIndex(index);
          return index < Count ? base[index] : null;
        }
        set
        {
          CheckIndex(index);
          if ( index < Count )
            base[index] = value;
          else
            CreateOutOfRange(index, value);
        }
      }
    
      private void CheckIndex(int index)
      {
        if ( index >= 0 ) return;
        throw new IndexOutOfRangeException(SysTranslations.IndexCantBeNegative.GetLang(nameof(NullSafeStringList), index));
      }
    
      private void CreateOutOfRange(int index, T value)
      {
        Capacity = index + 1;
        int count = index + 1 - Count;
        for ( int i = 0; i < count; i++ )
          Add(null);
        base[index] = value;
      }
    
    }
    

    And the following class:

    static class Languages
    {
    
      /// <summary>
      /// Indicate language codes.
      /// </summary>
      static public readonly NullSafeOfEnumDictionary<string, Language> Values;
    
      /// <summary>
      /// Indicate language codes.
      /// </summary>
      static public readonly NullSafeOfStringDictionary<Language> Codes;
    
      /// <summary>
      /// Indicate managed languages.
      /// </summary>
      static public readonly Language[] Managed;
    
      /// <summary>
      /// Indicate default language.
      /// </summary>
      static public readonly Language Default = Language.EN;
    
      /// <summary>
      /// Indicate current language code.
      /// </summary>
      static public string CurrentCode => Codes[Current];
    
      /// <summary>
      /// Indicate current language.
      /// </summary>
      static public Language Current
      {
        get
        {
          string lang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
          var result = Values[lang];
          if ( !Managed.Contains(result) ) result = Default;
          return result;
        }
      }
    
      /// <summary>
      /// Static constructor.
      /// </summary>
      static Languages()
      {
        try
        {
          Managed = Enums.GetValues<Language>().Skip(1).ToArray();
          Codes = new NullSafeOfStringDictionary<Language>(Managed.ToDictionary(v => v, v => v.ToString().ToLower()));
          Values = new NullSafeOfEnumDictionary<string, Language>(Codes.ToDictionary(v => v.Value, v => v.Key));
        }
        catch ( Exception ex )
        {
          MessageBox.Show(ex.message);
        }
      }
    
    }
    

    You can simplify and remove everything not needed.

    [Serializable]
    public class NullSafeOfEnumDictionary<TKey, TValue> : Dictionary<TKey, TValue>
      where TValue : Enum
    {
    
      public NullSafeOfEnumDictionary()
      {
      }
    
      public NullSafeOfEnumDictionary(int capacity) : base(capacity)
      {
      }
    
      public NullSafeOfEnumDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
      {
      }
    
      public NullSafeOfEnumDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary)
      {
      }
    
      public NullSafeOfEnumDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer)
      {
      }
    
      public NullSafeOfEnumDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer)
      {
      }
    
      protected NullSafeOfEnumDictionary(SerializationInfo info, StreamingContext context) : base(info, context)
      {
      }
    
      public new TValue this[TKey key]
      {
        get
        {
          return ContainsKey(key) ? base[key] : default;
        }
        set
        {
          if ( ContainsKey(key) )
            base[key] = value;
          else
            Add(key, value);
        }
      }
    }
    

    Usage:

    static class SysTranslations
    {
      static public readonly TranslationsDictionary NotImplemented
        = new TranslationsDictionary
        {
          [Language.EN] = "Not implemented: {0}",
          [Language.FR] = "Non implémenté : {0}",
        };
    }
    
    string msg = SysTranslations.NotImplemented.GetLang("...");
    

    Load/Write

    static class NullSafeOfStringDictionaryHelper
    {
      static public bool LoadKeyValuePairs(this NullSafeOfStringDictionary<string> list,
                                           string filePath,
                                           string separator,
                                           bool showError = true)
      {
        try
        {
          list.Clear();
          foreach ( string line in File.ReadAllLines(filePath) )
            if ( !line.StartsWith(";") && !line.StartsWith("//") )
            {
              var parts = line.SplitNoEmptyLines(separator);
              if ( parts.Length == 1 )
                list.Add(parts[0].Trim(), string.Empty);
              else
              if ( parts.Length == 2 )
                list.Add(parts[0].Trim(), parts[1].Trim());
              else
              if ( parts.Length > 2 )
                list.Add(parts[0].Trim(), string.Join(separator, parts.Skip(1)));
            }
          return true;
        }
        catch ( Exception ex )
        {
          if ( showError )
            MessageBox.Show(SysTranslations.LoadFileError.GetLang(filePath, ex.Message),
                            Globals.AssemblyTitle,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Warning);
          return false;
        }
      }
      static public bool SaveKeyValuePairs(this NullSafeOfStringDictionary<string> list,
                                           string filePath,
                                           string separator,
                                           bool showError = true)
      {
        using ( var stream = File.CreateText(filePath) )
          try
          {
            foreach ( var item in list )
              if ( !item.Key.StartsWith(";") && !item.Key.StartsWith("//") )
                stream.WriteLine(item.Key + separator + item.Value);
              else
                stream.WriteLine(item.Key);
            stream.Close();
            return true;
          }
          catch ( Exception ex )
          {
            if ( showError )
              MessageBox.Show(SysTranslations.LoadFileError.GetLang(filePath, ex.Message),
                              Globals.AssemblyTitle,
                              MessageBoxButtons.OK,
                              MessageBoxIcon.Warning);
            return false;
          }
      }
    }
    

    This last is is even more of a rough draft than the above.