    //@include lib_Class.js
    //@include lib_sol.common.ObjectUtils.js
    //@include lib_sol.common.Cache.js

    /*
     * Local definition of the class `sol.common.Cache` for backward compatibility of previous solution packages.
     */
    if (!sol.ClassManager.getClass("sol.common.Cache")) {
      sol.define("sol.common.Cache", {

        initialize: function (config) {
          var me = this;
          me.cache = new java.util.concurrent.ConcurrentHashMap(8, 0.9, 1);
        },

        /**
         * Inserts the specified key-value pair into the cache.
         * @param {String} key
         * @param {Object} value
         * @return {Object} The previous value associated with the key, or null if there was no mapping before
         */
        put: function (key, value) {
          var me = this;
          return me.cache.put(key, value);
        },

        /**
         * Inserts all key-value pairs specified by an object into the cache. Existing mappings will be replaced.
         * @param {Object} data Property names will be used as keys and the associated values as values.
         */
        putAll: function (data) {
          var me = this;
          me.cache.putAll(data);
        },

        /**
         * Tests if the specified object is a key in the cache.
         * @param {String} key
         * @return {Boolean}
         */
        containsKey: function (key) {
          var me = this;
          return me.cache.containsKey(key);
        },

        /**
         * Returns the value for the specified key from the cache, or null if the chache contains no mapping for the key.
         * @param {String} key
         * @return {Object}
         */
        get: function (key) {
          var me = this;
          return me.cache.get(key);
        },

        /**
         * Returns an enumeration of all keys in the cache.
         * @return {Object} An `java.util.Enumeration` of all keys
         */
        keys: function () {
          var me = this;
          return me.cache.keys();
        },

        /**
         * Returns a collection view of the values contained in the cache.
         * @return {Object} An `java.util.Collection` of all values
         */
        values: function () {
          var me = this;
          return me.cache.values();
        },

        /**
         * Returns an enumeration of the values in the cache.
         * @return {Object} An `java.util.Enumeration` of all values
         */
        elements: function () {
          var me = this;
          return me.cache.elements();
        },

        /**
         * Removes the key (and its corresponding value) from the cache.
         * @param {String} key
         * @return {Object} The previous value associated with the key, or null if there was no value for the key
         */
        remove: function (key) {
          var me = this;
          return me.cache.remove(key);
        },

        /**
         * Returns the number of key-value pairs in the cache.
         * @return {Number}
         */
        size: function () {
          var me = this;
          return me.cache.size();
        },

        /**
         * Returns `true` if the chache contains no key-value pairs.
         * @return {Boolean}
         */
        isEmpty: function () {
          var me = this;
          return me.cache.isEmpty();
        },

        /**
         * Removes all of the mappings from the cache.
         */
        clear: function () {
          var me = this;
          me.cache.clear();
        }
      });
    }

    /**
     * Cache for Translate Terms. This class handles localization in Business Solutions.
     *
     * Property files should be placed in `Administration\Localization\`.
     *
     * ELO internal projects must use `Administration\Localization\system`. Modifications and Partner implementations must
     * use `Administration\Localization\custom`.
     *
     * # Using localization in scripts
     *
     * Thanks to a require function call all required translation terms will get loaded in the cache. This must be done
     * before terms are used by scripts. Following example shows a common usage for a dynamic keyword list.
     *
     * A property file must be located in the localization folder. e.g. `Administration\Localization\system\sol.invoice.locale.properties`
     *
     *     sol.invoice.dynkwl.Company.ID=Company id
     *     sol.invoice.dynkwl.Company.NAME=Company name
     *     sol.invoice.dynkwl.Company.CITY=City
     *
     * The TranslateTerms utilizes the use of translation keys.
     *
     *     sol.common.TranslateTerms.require('sol.invoice.dynkwl.Company');
     *
     *     var tableHeaders = [
     *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.ID'),
     *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.NAME'),
     *       sol.common.TranslateTerms.translate('sol.invoice.dynkwl.Company.CITY')];
     *
     * @author PZ, ELO Digital Office GmbH
     * @version 1.03.000
     *
     * @eloix
     * @eloas
     *
     * @requires sol.common.ObjectUtils
     * @requires sol.common.Cache
     */
    sol.define("sol.common.TranslateTerms", {
      singleton: true,

      /**
       * Loads a list of translation keys by a given prefix.
       * Prefixes can be either passed as an array or string.
       * @param {String} prefixes
       * @param {String} additionalLanguage (optional)
       */
      require: function (prefixes, additionalLanguage) {
        var me = this,
            requestedCount, idx, findTranslateTermInfo, findResult, i, translateTerm, j, language, term;

        me.logger.enter("require", arguments);
        if (!prefixes) {
          me.logger.warn("Translation term key prefix not set; use prefix 'sol'.");
          prefixes = ["sol"];
        }

        if (!sol.common.ObjectUtils.isArray(prefixes)) {
          prefixes = [prefixes];
        }

        requestedCount = prefixes.length;
        me.getLangs(additionalLanguage);
        prefixes = me.filterPrefixes(prefixes);

        if (prefixes.length > 0) {
          me.logger.debug(["Load {0} of {1} requested prefixes", requestedCount, prefixes.length]);
          me.translateTerms = me.translateTerms || sol.create("sol.common.Cache");

          try {
            findTranslateTermInfo = new FindTranslateTermInfo();
            findTranslateTermInfo.terms = prefixes;
            findTranslateTermInfo.langs = me.languages;
            idx = 0;
            findResult = ixConnect.ix().findFirstTranslateTerms(findTranslateTermInfo, 100);
            while (true) {
              for (i = 0; i < findResult.translateTerms.length; i++) {
                translateTerm = findResult.translateTerms[i];
                for (j = 0; j < translateTerm.langs.length; j++) {
                  language = translateTerm.langs[j];
                  term = translateTerm.termLangs[j] || "";
                  if (!me.translateTerms.containsKey(language)) {
                    me.translateTerms.put(language, sol.create("sol.common.Cache"));
                  }
                  me.translateTerms.get(language).put(translateTerm.translationKey, term);
                }
              }
              if (!findResult.moreResults) {
                break;
              }
              idx += findResult.translateTerms.length;
              findResult = ixConnect.ix().findNextTranslateTerms(findResult.searchId, idx, 100);
            }
            me.rememberPrefixes(prefixes);
          } finally {
            if (findResult) {
              ixConnect.ix().findClose(findResult.searchId);
            }
          }
        } else {
          me.logger.debug("All prefixes have already been cached.");
        }
        me.logger.exit("require");
      },

      /**
       * @private
       * Retrieves the system languages
       * @param {String} additionalLanguage (optional) Additional language
       */
      getLangs: function (additionalLanguage) {
        var me = this,
            langsTerm, i, lang;
        me.logger.enter("getLangs", arguments);
        if (me.languages) {
          me.addLang(additionalLanguage);
        } else {
          me.languages = [];
          langsTerm = ixConnect.ix().checkoutTranslateTerms([TranslateTermC.GUID_SYSTEM_LANGUAGES], LockC.NO);
          for (i = 0; i < langsTerm[0].langs.length; i++) {
            lang = langsTerm[0].langs[i];
            me.addLang(lang);
          }
        }
        me.logger.exit("getLangs", me.languages);
      },

      /**
       * @private
       * Adds a language
       * @param {String} language Language
       */
      addLang: function (language) {
        var me = this;
        language = String(language || "");
        if (language) {
          if (me.languages.indexOf(language) < 0) {
            me.languages.push(language);
            delete me.downloadedPrefixes;
          }
        }
      },

      /**
       * @private
       * Checks which prefixes have already been loaded.
       * @param {String[]} prefixes
       * @return {String[]}
       */
      filterPrefixes: function (prefixes) {
        var me = this,
            filteredCount = 0,
            filtered;
        if (me.downloadedPrefixes) {
          filtered = [];
          prefixes.forEach(function (prefix) {
            if (!me.downloadedPrefixes.containsKey(String(prefix))) {
              filtered.push(prefix);
            } else {
              filteredCount++;
            }
          });
        } else {
          filtered = prefixes;
        }
        me.logger.debug(["Filtered '{0}' prefixes that have already been loaded", filteredCount]);
        return filtered;
      },

      /**
       * @private
       * Saves the prefixes that have been loaded, to avoid repeated requests.
       * @param {String[]} prefixes
       */
      rememberPrefixes: function (prefixes) {
        var me = this;

        if (!me.downloadedPrefixes) {
          me.downloadedPrefixes = sol.create("sol.common.Cache");
        }
        if (me.downloadedPrefixes) {
          prefixes.forEach(function (prefix) {
            me.downloadedPrefixes.put(String(prefix), true);
          });
        }
      },

      /**
       * Get a translation for a key by a given language code.
       *
       *     sol.common.TranslationTerms.getTerm('de', sol.contract.ix.client');
       *
       * @param {String|de.elo.ix.client.ClientInfo} language Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
       * @param {String} key The key in the resource files
       * @param {boolean} requestedTerm (optional) if not set, function requests term if not in list.
       * @return {String} result The value of the key
       */
      getTerm: function (language, key, requestedTerm) {
        var me = this,
            result;

        if (language instanceof ClientInfo) {
          language = language.language;
        }

        if (!language) {
          me.logger.warn("Language not set.");
          return key;
        }

        if (!me.translateTerms) {
          me.require();
        }

        if (!me.translateTerms.containsKey(language)) {
          me.translateTerms.put(language, sol.create("sol.common.Cache"));
        }

        result = me.translateTerms.get(language).get(key);
        if (!result) {
          if (!requestedTerm) {
            me.logger.debug("Translation key not found or cached. requesting key: " + key + ". This could be the case if used within templates. Please note that prefetching translation keys improves system performance.");
            me.require(key, language);

            return me.getTerm(language, key, true);
          } else {
            me.logger.warn("Translation key not found: " + key);
            return key;
          }
        }
        return result;
      },

      /**
       * Translates a key to the current language
       * @param {String} key The key in the resource files
       * @return {String} result The translated key
       *
       */
      translate: function (key) {
        var me = this,
            language, _result;

        me.logger.enter("translate", arguments);
        if (ixConnect) {
          language = ixConnect.loginResult.clientInfo.language;
        } else {
          throw "IX connection is not available.";
        }
        _result = String(me.getTerm(language, key));
        me.logger.exit("translate", _result);
        return _result;
      }
    });