1 <?php 2 class Lang_Auto_Detect 3 { 4 // основные переменные 5 // список поддерживаемых языков 6 public $lang = Array('en'=>array('English','http://en.wikipedia.org/wiki/English_language'), 7 'ru'=>array('Russian','http://ru.wikipedia.org/wiki/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA'), 8 'ua'=>array('Ukraine','http://uk.wikipedia.org/wiki/%D0%A3%D0%BA%D1%80%D0%B0%D1%97%D0%BD%D1%81%D1%8C%D0%BA%D0%B0_%D0%BC%D0%BE%D0%B2%D0%B0') 9 ); 10 // порог чувствительности, сколько в % должно быть символов языка, чтобы он был определён 11 public $detect_range = 75; 12 // обрабатывать ли многоязычные документы и возвращать массив используемых языков 13 public $detect_multi_lang = false; // пока не реализовано 14 // возвращать все результаты и вероятности 15 public $return_all_results = false; // в реальном применении лучше отключить 16 // использовать дополнительно систему правил и исключений 17 public $use_rules = false; 18 //применять только правила (быстрее намного, но результат менее вероятен, чем больше текста, тем достовернее) 19 public $use_rules_only = false; 20 // приоритет правил над статистикой - 21 public $use_rules_priory = true; // true - правила приоритетнее статистики, false - статистика перед правилами 22 // искать только первое правило или максимум совпадений? 23 public $match_all_rules = false; // только одно иначе = все 24 //использовать % от алфавита или общее количество символов каждого алфавита 25 public $use_str_len_per_lang = true; // true - использовать общую длину текста приоритетнее, чем % от символов алфавита, false - наоборот 26 27 // минимальная длина строки для детектирования 28 public $min_str_len_detect = 50; 29 // для обеспечения нормальной производительности задайте максимальную длину в символах для сравнения 30 public $max_str_len_detect = 1680; // 31 32 33 // внутренняя переменная - таблица алфавитов используемых при определении 34 private $_langs = array( 35 'en'=>array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'), 36 'ru'=>array('а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я'), 37 'ua'=>array('а','б','в','г','ґ','д','е','є','ж','з','и','і','ї','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ь','ю','я') 38 ); 39 40 // хранит правила 41 // правила это символы или строки, наличие которой (любой или всех) автоматически влечёт идентификацию текста 42 private $_lang_rules = array( 43 'en'=>array('th', 'ir'), 44 'ru'=>array('ъ', 'ё' ), 45 'ua'=>array('ї', 'є') 46 ); 47 48 49 // конструктор класса 50 public function __construct() 51 { 52 return true; 53 } 54 55 // подготовка введённой строки для сравнения 56 private function _prepare_str($tmp_str = null) 57 { 58 if ($tmp_str == null) return false; // если ничего не передали - выйти 59 60 $tmp_str = trim($tmp_str); 61 $tmp_encoding = mb_detect_encoding($tmp_str); 62 63 if (mb_strlen($tmp_str, $tmp_encoding) > $this->max_str_len_detect) 64 { 65 //обрезать длину текста, для производительности 66 $tmp_str = mb_substr($tmp_str, 0, $this->max_str_len_detect, $tmp_encoding); 67 } 68 else 69 if (mb_strlen($tmp_str, $tmp_encoding) <= $this->min_str_len_detect) return false; 70 71 // конвертируем кодировки 72 $tmp_str = mb_convert_encoding($tmp_str, 'UTF-8', $tmp_encoding); 73 74 // приводим все к нижнему регистру 75 $tmp_str = mb_strtolower($tmp_str, 'UTF-8'); 76 77 return $tmp_str; 78 } 79 80 // функция определения языка по правилам 81 // правила однозначно определяют язык, однако могут ошибаться :) 82 private function _detect_from_rules($tmp_str = null) 83 { 84 if ($tmp_str == null) return false; // если ничего не передали - выйти 85 if (!is_array($this->_lang_rules)) return false; 86 87 // перебор всех правил 88 foreach ($this->_lang_rules as $lang_code=>$lang_rules) 89 { 90 $tmp_freq = 0; 91 92 foreach ($lang_rules as $rule) 93 { 94 $tmp_term = mb_substr_count($tmp_str, $rule); 95 96 if ($tmp_term > 1) // то есть символ в строе 1 или более раз 97 { 98 $tmp_freq++; // увеличим счётчик символов языка, которые в этой строке есть 99 } 100 101 // теперь проверим 102 if ($this->match_all_rules === true) 103 { 104 // нужно совпадение всех правил 105 if ($tmp_freq == count($lang_rules)) return $lang_code; 106 } 107 else 108 { 109 // достаточно одного 110 if ($tmp_freq > 0) return $lang_code; 111 } 112 } 113 } 114 115 return false; 116 } 117 118 // функция определения языка по таблице 119 private function _detect_from_tables($tmp_str = null) 120 { 121 if ($tmp_str == null) return false; // если ничего не передали - выйти 122 123 //мы уже должны ранее обработать строку для сравнения 124 // перебираем все языки и для каждого определим вероятность 125 $lang_res = array(); 126 127 foreach ($this->lang as $lang_code=>$lang_name) 128 { 129 $lang_res[$lang_code] = 0; //по умолчанию 0, то есть не этот язык 130 131 $tmp_freq = 0; // частота символов текущего языка 132 $full_lang_symbols = 0; //полное количество символов этого языка 133 134 // так как длина строки может быть произвольной, а алфавит одинаковый, то цикл по алфавитам 135 $cur_lang = $this->_langs[$lang_code]; 136 137 foreach ($cur_lang as $l_item) 138 { 139 // теперь посмотреть количество вхождений символа в строку 140 $tmp_term = mb_substr_count($tmp_str, $l_item); 141 142 if ($tmp_term > 1) // то есть символ в строе 1 или более раз 143 { 144 $tmp_freq++; // увеличим счетчик символов языка, которые в этой строке есть 145 $full_lang_symbols += $tmp_term; 146 } 147 } 148 149 if ($this->use_str_len_per_lang === true) 150 { 151 //использовать общее количество символов 152 $lang_res[$lang_code] = $full_lang_symbols; 153 } 154 else 155 // Вычислить процент от всех символов алфавита 156 $lang_res[$lang_code] = ceil((100 / count($cur_lang) ) * $tmp_freq); 157 158 } 159 160 // так, теперь посомтрим что вышло 161 arsort($lang_res, SORT_NUMERIC); //сортируем массив первый элемент язык с большей вероятностью 162 163 if ($this->return_all_results == true) 164 { 165 return $lang_res; // если вернуть все результаты - возвращаем, иначе выбрать лучший 166 } 167 else 168 { 169 // если больше указанного нами порога, возвратить код языка, иначе - null (то есть, мы не можем определить код языка) 170 $key = key($lang_res); 171 172 if ($lang_res[$key] >= $this->detect_range) 173 return $key; 174 else 175 return null; 176 } 177 178 } 179 180 181 // общая функция для определения языка 182 public function lang_detect($tmp_str = null) 183 { 184 if ($tmp_str == null) return false; // если ничего не передали - выйти 185 186 $tmp_str = $this->_prepare_str($tmp_str); 187 188 if ($tmp_str === false) return false; 189 190 // если правила применяем ДО таблицы 191 if ($this->use_rules_only === true) 192 { 193 $res = $this->_detect_from_rules($tmp_str); 194 195 return array($res, $this->lang[$res]); 196 } 197 else 198 { 199 // при использовании таблиц мы не можем получить полную раскладку по результатам, потому отключаем 200 $this->return_all_results = false; 201 202 $res = $this->_detect_from_tables($tmp_str); 203 204 if ($tmp_str === false) return false; 205 206 if ($this->use_rules === true) 207 { 208 $res_rules = $this->_detect_from_rules($tmp_str); 209 210 // исходим из настроек приоритета правил и статистики 211 if ($this->use_rules_priory === true) 212 { 213 //правила имеют бОльший вес, чем статистика 214 return array($res_rules, $this->lang[$res_rules]); 215 } 216 else 217 { 218 return array($res, $this->lang[$res]); 219 } 220 } 221 else 222 return array($res, $this->lang[$res]); 223 } 224 } 225 } 226 ?>