/// <summary>
        /// Ivalidates app localizations cache
        /// </summary>
        /// <param name="appName"></param>
        public static void InvalidateAppLocalizationsCache(string appName, string className)
        {
            //app localizations cache first

            if (AppLocalizationsCache.ContainsKey(appName) && string.IsNullOrEmpty(className))
            {
                AppLocalizationsCache.Remove(appName);
            }
            else if (AppLocalizationsCache.ContainsKey(appName))
            {
                AppLocalizationsCache.Remove(appName);

                //FIXME - this requires better reading - need to read from db to 'top up' the cache

                //var localizations = AppLocalizationsCache[appName].ToList();

                //var toBeRemoved = localizations.FirstOrDefault(l => l.ClassName == className);
                //if(toBeRemoved != null)
                //    localizations.Remove(toBeRemoved);

                //AppLocalizationsCache[appName] = localizations;
            }

            //clients cache too...
            var keysToInvalidate = ClientLocalizationsCache.Keys.Where(k => k.IndexOf(appName) > -1).ToList();

            foreach (var key in keysToInvalidate)
            {
                ClientLocalizationsCache.Remove(key);
            }
        }
        /// <summary>
        /// Gets localizations for specified lang codes and apps
        /// </summary>
        /// <typeparam name="TDbCtx"></typeparam>
        /// <param name="dbCtx"></param>
        /// <param name="langCodes"></param>
        /// <param name="appNames"></param>
        /// <returns></returns>
        public static async Task <Dictionary <string, Dictionary <string, Dictionary <string, string> > > > GetAppLocalizationsAsync <TDbCtx>(TDbCtx dbCtx, IEnumerable <string> langCodes, IEnumerable <string> appNames)
            where TDbCtx : DbContext, ILocalizedDbContext
        {
            var ret = new Dictionary <string, Dictionary <string, Dictionary <string, string> > >();

            if (appNames == null || !appNames.Any())
            {
                return(ret);
            }

            //grab the default lng
            var defaultLang = await Lang.GetDefaultLangAsync(dbCtx);

            if (langCodes == null || !langCodes.Any())
            {
                if (defaultLang == null)
                {
                    return(ret);
                }

                //no langs provided, so the calling client may not be aware of the lang yet.
                //in this scenario just lookup the default lang and get the localization fot the default lng
                langCodes = new[] { defaultLang.LangCode };
            }


            //see if there is cache for the current combination already
            var cacheKey = GetClientLocalizationsCacheKey(langCodes, appNames);

            if (ClientLocalizationsCache.ContainsKey(cacheKey))
            {
                return((Dictionary <string, Dictionary <string, Dictionary <string, string> > >)ClientLocalizationsCache[cacheKey]);
            }

            //fetch localizations if needed
            foreach (var appName in appNames)
            {
                if (!AppLocalizationsCache.ContainsKey(appName) || AppLocalizationsCache[appName] == null)
                {
                    //read all the Localization classes for the given AppName
                    var localizationClasses = await
                                              dbCtx.LocalizationClasses.Where(lc => lc.ApplicationName == appName).ToListAsync();

                    //now grab the identifiers - need them in order to request translation keys. when using IQueryable, the range MUST BE static, simple types
                    //so even though compiler will not complain if a range is passes as localizationClasses.Select(lc => lc.Uuid) it will fail in the runtime
                    //saving this to a variable solves the issue!
                    var localizationClassesIdentifiers = localizationClasses.Select(lc => lc.Uuid);

                    var translationKeys = await
                                          dbCtx.TranslationKeys
                                          .Where(tk =>
                                                 localizationClassesIdentifiers.Contains(tk.LocalizationClassUuid) &&
                                                 (tk.Inherited != true || tk.Inherited == true && tk.Overwrites == true) //only output not inherited or inherited overwrites!
                                                 )
                                          .ToListAsync();

                    AppLocalizationsCache[appName] = localizationClasses.GroupJoin(
                        translationKeys,
                        lc => lc.Uuid,
                        tk => tk.LocalizationClassUuid,
                        (lc, tk) => new LocalizationClass()
                    {
                        ApplicationName    = lc.ApplicationName,
                        ClassName          = lc.ClassName,
                        InheritedClassName = lc.InheritedClassName,
                        TranslationKeys    = tk
                    }
                        );
                }

                var appLocalizations = AppLocalizationsCache[appName];

                foreach (var appL in appLocalizations)
                {
                    var key = $"{appL.ApplicationName}.{appL.ClassName}";
                    if (!ret.ContainsKey(key))
                    {
                        ret[key] = new Dictionary <string, Dictionary <string, string> >();
                    }
                    var classTranslations = ret[key];


                    foreach (var tk in appL.TranslationKeys)
                    {
                        if (!classTranslations.ContainsKey(tk.Key))
                        {
                            classTranslations[tk.Key] = new Dictionary <string, string>();
                        }

                        foreach (var translation in (tk.Translations ?? new Translations()).Where(t => t.Key == defaultLang.LangCode || langCodes.Contains(t.Key)))
                        {
                            classTranslations[tk.Key].Add(translation.Key, translation.Value);
                        }
                    }
                }
            }

            //cahce the output
            ClientLocalizationsCache[cacheKey] = ret;

            return(ret);
        }