 Translit = {};
        //АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
    Translit.ruTable = ["A","B","V","G","D","E","Yo","Zh","Z","I","J","K","L","M","N","O","P","R","S","T",
        "U","F","H","C","Ch","Sh","Shh","","Y","","E","Yu","Ya"]; // как в Wiki, без сокр. окончаний. остальное - см. Ru2EnCharDef
        //АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ
    Translit.uaTable = ["A","B","V","H","G","D","E","Ye","Zh","Z","Y","I","Yi","Y","K","L","M","N","O","P","R","S",
        "T","U","F","Kh","Ts","Ch","Sh","Shch","","Yu","Ya"]; // по кабмину, остальное - см. Ua2EnCharDef
    Translit.exStates = "";
    Translit.exReplaces = [];
     
    Translit.lastInSet = function (set, src) {
        return src.length == 0 ? false : set.indexOf(src.charAt(src.length-1).toUpperCase(), 0) >= 0;
    };  
     
    Translit.transliterate = function (s, id, states, replaces, charTrans, wordTrans) {
        var originalWord = "";
        var resultStr = "";
        var i = 0;
       
        function handleWord() {
            var upperWord = originalWord.toUpperCase();
            var lastUpper = false;
            var curUpper = false;
            var cache = "";
            var word = "";
            var ch = "";
            var index = -1;
        
            function post() {
                if (curUpper && lastUpper) cache = cache.toUpperCase();
                if (!lastUpper) cache = cache.toLowerCase();
                word += cache;
                cache = "";
            };
        
            for (var i = 0; i < originalWord.length; i++) {
                ch = upperWord.charAt(i);
                curUpper = (ch == originalWord.charAt(i));
                if (i > 0) post();
                index = states.indexOf(ch, 0);
                if (charTrans) {
                    cache = charTrans(id, originalWord, upperWord, i, replaces[index]).toString();
                } else cache = replaces[index];
                lastUpper = curUpper;
            };
       
            post();
      
            if (wordTrans) word = wordTrans(id, originalWord, upperWord, word);
       
            return word;
        };
      
        do {
            while ((i < s.length) && this.lastInSet(states, s[i])) {
                originalWord += s[i];
                i++;
            };
            if (originalWord != "") resultStr += handleWord();
            originalWord = "";
         
            while ((i < s.length) && !this.lastInSet(states, s[i])) {
                if ((index = this.exStates.indexOf(s[i])) < 0) {
                  resultStr += s[i];
                } else resultStr += this.exReplaces[index];
                i++;
            };
         
           if (i >= s.length) break;
       } while (true);
       
       return resultStr;
     };
     
    Translit.Ru2EnCharDef = function (id, word, upperWord, index, def) {
        return ((upperWord.charAt(index) == "Е") && ((index == 0) ||
          Translit.lastInSet("АЯУЮЭЕЫИОЁ", upperWord.charAt(index - 1)))) ? "Ye" : def;
    };
     
    Translit.Ru2EnChar = Translit.Ru2EnCharDef;
     
    Translit.Ru2EnWordDef = function (id, word, upperWord, def) {
        return def;
    };
     
    Translit.Ru2EnWord = Translit.Ru2EnWordDef;
     
    Translit.Ru2En = function (s) {
        return this.transliterate(s, "RU2EN", "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", this.ruTable, this.Ru2EnChar, this.Ru2EnWord);
    };
    
    Translit.Ua2EnCharDef = function (id, word, upperWord, index, def) {
        var idx = -1;
        return ((index > 0) && ((idx = "ЄЇЙЮЯ".indexOf(upperWord.charAt(index)))>= 0)) ? 
            ["Ie","I","I","Iu","Ia"][idx] : def;    
    };
    
    Translit.Ua2EnChar = Translit.Ua2EnCharDef;
    
    Translit.Ua2EnWordDef = function (id, word, upperWord, def) {
        return def; 
    };
    
    Translit.Ua2EnWord = Translit.Ua2EnWordDef;
    
    Translit.Ua2En = function (s) {
        return this.transliterate(s, "UA2EN", "АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ", this.uaTable, this.Ua2EnChar, this.Ua2EnWord);
    };
    
    Translit.mapSpecials = function(specialChars, replaceTo) {
        var index;
        var ch;
        for (var i = 0; i < specialChars.length; i++) {
            if ((index = this.exStates.indexOf(ch = specialChars.charAt(i))) < 0) {
                this.exStates += ch;
                this.exReplaces.push(replaceTo);
            } else this.exReplaces[index] = replaceTo;
        }; 
    };
    
    Translit.unmapSpecials = function(specialChars) {
        var newExStates = "";
        var newExReplaces = [];
        var ch;
        for (var i = 0; i < this.exStates.length; i++) {
            if (specialChars.indexOf(ch = this.exStates.charAt(i)) < 0) {
                newExStates += ch;
                newExReplaces.push(this.exReplaces[i]);
            };
        };
        this.exStates = newExStates;
        this.exReplaces = newExReplaces;
    };

