Определение языка и кодировки. Компонент для CakePHP
Для моего текущего проекта необходимо определять на каком языке пользователь вводит информацию. Причём это не сложный выбор между PHP и Perl, а, например, между английским и испанским. Сначала я хотел составить список самых распространённых слов в популярных языках - предлоги, частые глаголы и т.д. Почти сразу я понял, что точность будет небольшая, а работы - очень много, даже, если её буду делать не я :).
Поэтому пришлось думать дальше. Мне больше всего понравился способ, в котором учитывается частотность букв, двух-, трёх- и четырёхбуквенных сочетаний.
Сначала система обучается - ей скармливается много текста и указывается, какой это язык. Она разбирает их на части и запоминает наиболее часто встречаемые как эталонные для этого языка.
Потом берётся текст с неизвестным языком и определяются наиболее часто встречаемые части в нём. Эти части сравниваются с эталонными и на основе этого определяется вероятность каждого из языков.
Я взял за основу код с http://boxoffice.ch/pseudo/ng.php, удалил лишнее, немного оптимизировал алгоритм и сделал код красивый и подходящий для CakePHP.
Определим язык для текста
-
$language = $this->LangDetect->detect('Учи олбанский и убей сибя ап стену.');
Не каждый человек сможет понять, что это за язык :). А компонент понял - russian-utf8.
Кстати, сразу видно бонус - определяется кодировка. Для русского поддерживаются ISO, KOI8-R, UTF-8, Windows-1251.
Если надо определить несколько наиболее вероятных языков, то надо указать второй параметр true.
-
$language = $this->LangDetect->detect('Учи олбанский и убей сибя ап стену.', true);
Это выдаст:
-
(
-
[russian-utf8] => 30366
-
[bulgarian-utf8] => 31619
-
[ukrainian-utf8] => 33273
-
[serbian_cyrillic-utf8] => 33878
-
[belarusian-utf8] => 35596
-
...
Чем меньше значение, тем более вероятно, что это тот язык.
Полный список поддерживаемых языков (можно получить с помощью $this->LangDetect->listLanguages()):
afrikaans, albanian, alemannic, amharic-utf8, arabic-iso8859_6, arabic-utf8, arabic-windows1256, armenian, armenian-utf8, basque, belarusian-utf8, belarusian-windows1251, bosnian, breton, bulgarian-iso8859_5, bulgarian-utf8, catalan, chinese-big5, chinese-gb2312, chinese-utf8, croatian-ascii, czech-iso8859_2, czech-utf8, danish, dutch, english, esperanto, estonian, finnish, french, frisian, georgian, georgian-utf8, german, greek-iso8859_7, greek-utf8, hawaian, hebrew-iso8859_8, hebrew-utf8, hindi, hindi-utf8, hungarian, icelandic, indonesian, irish_gaelic, italian, japanese-euc_jp, japanese-shift_jis, japanese-utf8, korean, korean-utf8, latin, latvian, lithuanian, malay, manx, marathi, marathi-utf8, middlefrisian, mingo_iroquois, nepali, nepali-utf8, norwegian, persian, persian_farsi-utf8, persian_farsi-windows1256, polish-iso8859_2, polish-utf8, portuguese_brazil, portuguese_europe, quechua, romanian, rumantsch, russian-iso8859_5, russian-koi8_r, russian-utf8, russian-windows1251, sanskrit, scots, scots_gaelic, serbian-ascii, serbian_cyrillic-utf8, slovak-ascii, slovak-utf8, slovak-windows1250, slovenian-ascii, slovenian-iso8859_2, spanish, swahili, swedish, tagalog, tamil, tamil-utf8, thai, thai-utf8, turkish, turkish-utf8, ukrainian-koi8_u, ukrainian-utf8, vietnamese, welsh, yiddish-utf8
Чтобы компонент заработал, нужно скачать скомпилированную информацию о языках и положить этот fingerprint.dat в app/vendors.
А вот и сам компонент:
-
<?php
-
-
/**
-
* Language detection component
-
*
-
* Most of code was copied from http://boxoffice.ch/pseudo/code_expl/code_class.php
-
* but a lot of things were simplified and beautified
-
*
-
* @link http://php.southpark.com.ua
-
* @author Vladimir Luchaninov
-
* @version 1.0 (3 Dec 2007)
-
*
-
*/
-
class LangDetectComponent {
-
protected $fingerprint = null;
-
-
//reasonable defaults
-
public $ngramCount = 350; //default nb of ngrams created from analyzed text
-
public $maxDelta = 140000; //stop evaluation deviate strongly
-
-
function startup(&$controller) {
-
}
-
-
/**
-
* Main function
-
*
-
* @param string $text Text with unknown language
-
* @param bool $onlyBest
-
* true - detect the best language
-
* false - detect all languages with possibilities
-
* @return LangDetect
-
*/
-
function detect($text, $onlyBest = true) {
-
return false;
-
}
-
-
$this->createNGrams($text);
-
-
if ($onlyBest){
-
return $this->compareNGramsOne();
-
} else {
-
return $this->compareNGrams();
-
}
-
}
-
-
/**
-
* Get list of the available languages
-
*
-
* @return array List of languages
-
*/
-
function listLanguages() {
-
return $languages;
-
}
-
-
/**
-
* Create ngram-array of given string
-
*
-
* @param string $text
-
*
-
*/
-
protected function createNGrams($text) {
-
foreach($array_words as $word) {
-
$word = "_". $word . "_";
-
for ($i=0; $i <$wordLength; $i++) { //start position within word
-
for ($s=1; $s <4+1; $s++) { //length of ngram
-
if (($i + $s) <$wordLength + 1) { //length depends on postion
-
}
-
}
-
}
-
}
-
-
//count-> value(frequency, int)... key(ngram, string)
-
-
//sort array by value(frequency) desc
-
-
//use only top frequent ngrams (def by $ng_number)
-
-
foreach ($top as $keyvar => $valvar){
-
$this->ngrams[] = $keyvar;
-
}
-
}
-
-
/**
-
* Compare ngrams: Textinput vs lm-files.
-
*
-
* @return array of languages with lowest deviation
-
*/
-
protected function compareNGrams() {
-
$limit = $this->maxDelta;
-
foreach ($this->fingerprint as $basename => $language) {
-
$delta = 0;
-
//compare each ngram of input text to current lm-array
-
foreach ($this->ngrams as $key => $ngram){
-
//match
-
//no match
-
} else {
-
$delta += 400;
-
}
-
//abort: this language already differs too much
-
if ($delta> $this->maxDelta) {
-
break;
-
}
-
} // End comparison with current language
-
-
//include only non-aborted languages in result array
-
if ($delta <($this->maxDelta)-400) {
-
$result[$basename] = $delta;
-
}
-
} //End comparison all languages
-
-
} else {
-
}
-
-
return $result;
-
}
-
-
/**
-
* Variation - COMPARE ng's - Return 1 LANGUAGE only
-
*
-
* @return string Most probable language
-
*/
-
protected function compareNGramsOne() {
-
$limit = 160000;
-
foreach ($this->fingerprint as $basename => $language) {
-
$delta = 0;
-
foreach ($this->ngrams as $key => $ngram){
-
} else {
-
$delta += 400;
-
}
-
if ($delta> $limit) {
-
break;
-
}
-
}
-
-
if ($delta <$limit) {
-
$result[$basename] = $delta;
-
$limit = $delta; //lower limit
-
}
-
}
-
-
return 'unknown';
-
} else {
-
//basename of best matching lm file
-
}
-
-
return $result_first;
-
}
-
-
}
-
?>
Картинка по теме: "TOP 10 языков технической документации"

интересный алгоритм (:
но не могу представить - где можно применить...
Здорово. Как раз на днях искал решение!
Решил отложить, не напрасно.
Спасибо.
@dkrnl:
Взять тот же deli.cio.us. Я подписался на rss по тегу cakephp, но около половины - информация на испанском и японском. Я бы с удовольствием поставил бы галочки "Смотреть информацию только на английском, русском, украинском". Кстати, надо будет сделать себе фильтр и ещё убирать дубликаты с разным названием, когда появится свободное время :))
Также, если брать информацию с других русскоязычных сайтов, то желательно знать кодировку и иногда надо отсеять ненужные для российских пользователей кириллические языки (например, белорусский, украинский). Не кидайтесь камнями, украинский мне очень нравится (по-моему, было какое-то международное исследование и украинский назвали вторым по мелодичности европейским языком после итальянского)
[...] Как на CakePhp определить язык? [...]
Вопрос в том на сколько длинным должен быть текст, чтобы правильно определить язык?
Чем длиннее, тем лучше
У меня нормально определяет, если есть хотя бы несколько предложений.
а что с копирайтом/лицензией на этот код и fingerprints ?
Большая часть кода взята с http://boxoffice.ch/pseudo/code_functions.php
Fingerprints тоже оттуда, но я их упаковал в один файл.
На том сайте я не нашёл упоминания о лицензиях. И вроде это просто научный проект, зарабатывать на нём никак не пытаются.
Но на самом деле алгоритм довольно простой, а fingerprints можно сделать самому - это тоже несложно.
Поэтому не думаю, что они захотят ругаться по поводу лицензии.
просто я собираюсь запаковать это для ALT Linux. ОК. поставлю Public Domain
ЗЫ.писал такое когда-то на C, но детектил только кодировку русского языка. Вся засада была именно в "хороших" fingerprints для разных языков.
Привет dear Vladimir,
Well done! fantastic work you did with my few lines of code: Congratulations.
Don't worry about the license. I appreciate your reference to http://www.boxoffice.ch/pseudo/
До свидания and Greetings from Швейцария
Mr BoxOffice
"Qui prior est tempore, potior est jure" помогите найти перевод этого выражения, пожалуйста. у меня ничего не выходит.
> Я взял за основу код с http://boxoffice.ch/pseudo/ng.php, удалил лишнее
Что оказалось лишним?
Марго, это латинский. Попробуй искать отдельно по словам, благо словарей в интернете хоть отбавляй. Если тратить по 4 минуты на слово, то минут за 20 справишься. "est" скорее всего "это". Думаю, это какая-то пословица или крылатое выражение.
Qui - похоже на английскоге "наше"...
Короче, что-то вроде "наша честь - бла, а вот бла-бла - это блаа."
Марго:
"Кто раньше по времени, тот прежде по праву"
поисковик выдал сразу