Рубрики
Без рубрики

Вклад в Gatsby – выпуск № 21311

Стротная проблема подсчета слов на японском языке. Tagged с JavaScript, OpenSource, Gatsby, React.

Я думаю, что есть много людей, которые хотели бы внести свой вклад в проект с открытым исходным кодом, но не знают, как и с чего начать, особенно если это их первый раз. Еще сложнее, когда английский, в котором происходит общение о проекте, не является вашим родным языком.

В Интернете не так много историй о том, как обычно происходит процесс. Несколько дней назад я нашел Этот пост Тан Ли Хау о том, чтобы внести свой вклад в Svelte и подумал, что это отличная идея подробно написать о том, как выглядит исправление ошибок или реализацию функции. Итак, в этом и предстоящих постах я расскажу вам о том, как я внес свой вклад в некоторые проекты OSS. Я надеюсь, что это побудит больше людей внести свой вклад.

На этот раз я собираюсь поговорить о своем первом (и не последнем) вкладе в Гэтсби Анкет

В своем стремлении познакомиться с (довольно большой) кодовой базой Gatsby, я решил просмотреть некоторые открытые проблемы и найти их, которые я мог бы исправить. Еще до того, как я смог найти хорошего, я сам столкнулся с ошибкой и решил сразу же исправить. Вот Проблема, о которой я собираюсь поговорить о Анкет

Фон

Я живу и работаю в Японии и должен общаться с другими разработчиками фронта. Хотя я не пишу свои посты на японском языке, я подумал, что когда -нибудь я мог бы захотеть и решил проверить, как будет выглядеть пост на японском языке. Я заметил что-то странное: дисплей времени на чтение было далеко от воспринимаемой ценности. Там нет ни одного на Этот блог больше Но вы можете увидеть это на Чрезмерная отреагирована Блог, например. Английский аналог одного и того же поста показал вдвое меньше времени, но чувствовал себя близко к реальности. Я понял, что японская ценность не правда, и должна быть проблема с тем, как Gatsby-Transformer-Remark , плагин, который я использую, чтобы отобрать свои сообщения на маркеун, считает слова … Верно. Слова Слова на английском разделены космосом, но не на японском языке. Это должно быть причиной.

Я был уверен, что мне нужно было найти ту часть, где происходит подсчет. Поэтому я погрузился в код Gatsby-Transformer-Remark Анкет Поскольку это неприятный плагин, он расположен внутри Gatsby Monorepo. Просто поиск время читать Я быстро нашел соответствующий код, который был довольно маленьким. Это внутри gatsby-transformer-remark/src/extend node-type.js :

return getHTML(markdownNode).then((html) => {
  let timeToRead = 0;
  const pureText = sanitizeHTML(html, { allowTags: [] });
  const avgWPM = 265;
  const wordCount =
    _.words(pureText).length +
    _.words(pureText, /[\p{sc=Katakana}\p{sc=Hiragana}\p{sc=Han}]/gu).length;
  timeToRead = Math.round(wordCount / avgWPM);
  if (timeToRead === 0) {
    timeToRead = 1;
  }
  return timeToRead;
});

Исправление ошибки

Похоже, кто -то уже пытался решить проблему подсчета слов CJK. Но почему это все еще неправильно? Я гуглил Документация для Лодаш S слова Функция:

_.words([string=''], [pattern])

Splits string into an array of its words.

Arguments

    [string=''] (string): The string to inspect.
    [pattern] (RegExp|string): The pattern to match words.

Он мало что говорит о том, что он делает, когда шаблон соответствует, поэтому я просто копировал эту строку

_.words("京都", /[\p{sc=Katakana}\p{sc=Hiragana}\p{sc=Han}]/gu);

в реплику внутри документов и проверил результат. Результат был ["京", "都"] Хотя 京都 это одно слово, которое означает Киото на японском языке. Видимо Лодаш Просто расщепляет строку всякий раз, когда шаблон соответствует персонаж . Это совершенно неправильно, потому что слова могут состоять из большего этого одного персонажа. Ну, я был бы удивлен, если Лодаш Считал это правильно. Но как я могу это исправить?

Как вы уже догадались, это очень сложная проблема, даже в исследованиях NLP (обработка естественного языка). Все виды машинного обучения используются для обучения токенизатора, который может правильно разделить строку на японские «слова».

На моей предыдущей работе я был вовлечен в такую работу и знаю, насколько это может быть сложно. Есть библиотека JavaScript под названием Куромоджи Это может проанализировать японские струны, но он не маленький и на самом деле не работал в момент исправления ошибки.

Очевидно, что нам не нужен полноценный морфологический анализатор, чтобы справиться с одним полем внутри плагина. Более того, Куромоджи Работает только с японцем, но мы также в идеале хотим поддержать китайцев, к которому применяются разные правила. Это означает, что нам нужна эвристика.

Хороший способ проверить, хороша ли некоторая эвристика – это сделать как вашу эвристическую функцию, так и настоящий морфологический анализатор работы по одной и той же строке. После немало Googling мне удалось найти небольшую библиотеку TinySegmenter это Саганы японские И просто разбивает его на морфемы, ничего особенного. Я использовал его, чтобы получить правильный счет, на котором я могу проверить свою эвристику.

Так как же выглядит моя эвристика?

Прежде всего, нелегко определить «слово» на японском языке. Глагол или прилагательное могут иметь много частей, некоторые из которых можно считать «словами» самостоятельно. Тем не менее, большая часть существительных состоят из двух персонажей, таких как 京都 (Киото) выше – 京 и 都. Таким образом, самая наивная эвристика просто посчитала бы каждого персонажа в строке, а затем разделит его на два:

const totalCount = _.words(latinChars).length + cjChars.length * 0.5;

Это то, что я сделал. Удивительно, но это показывает цифры, не отличающиеся от того, что показал настоящий анализатор. Однако в более крупных текстах это все еще немного ощущалось. Это потому, что прилагательные и глаголы обычно длиннее 2 символов. Так что я настраивал его и получил значение 0,56 Анкет Даже в тексте поста размером с пост в блоге он был очень близок к «реальной» значению. Конечно, нам не нужен плагин, чтобы точно рассказать, сколько времени нужно, чтобы прочитать пост – это не то, за что мы пришли.

После преобразования значения в протоколы, используя среднюю постоянную константу слова в минуту (которой я только что решил доверять)

const avgWPM = 265;

У меня было количество времени, которое сейчас было более или менее таким же, как английский аналог. Хороший!

Мне все еще нужно было выяснить, как разделить количество латинских и японских (или, чтобы быть точными как Хань, так и Хирагана/Катакана). Вот где я вспомнил. Существует набор диапазонов Unicode для всех видов систем письма, которые я видел в Красноречивая JavaScript Удивительная книга о JavaScript от Marijn Haverbeke, книга, которую я использовал для изучения JavaScript! Это было очень интересное чувство, чтобы вернуться к нему через 2 года.

Вот набор диапазонов Unicode Я использовал. Я выбрал Хан, Хирагана и Катакана в диапазоне от него и написал функцию, которая помещает символы строки в отдельный массив.

Вот что финальная версия в запрос на тягу, которую я отправил похоже:

const sanitizeHTML = require(`sanitize-html`);
const _ = require(`lodash`);

// Unicode ranges for Han (Chinese) and Hiragana/Katakana (Japanese) characters
const cjRanges = [
  [11904, 11930], // Han
  [11931, 12020],
  [12032, 12246],
  [12293, 12294],
  [12295, 12296],
  [12321, 12330],
  [12344, 12348],
  [13312, 19894],
  [19968, 40939],
  [63744, 64110],
  [64112, 64218],
  [131072, 173783],
  [173824, 177973],
  [177984, 178206],
  [178208, 183970],
  [183984, 191457],
  [194560, 195102],
  [12353, 12439], // Hiragana
  [12445, 12448],
  [110593, 110879],
  [127488, 127489],
  [12449, 12539], // Katakana
  [12541, 12544],
  [12784, 12800],
  [13008, 13055],
  [13056, 13144],
  [65382, 65392],
  [65393, 65438],
  [110592, 110593],
];

function isCjChar(char) {
  const charCode = char.codePointAt(0);
  return cjRanges.some(([from, to]) => charCode >= from && charCode < to);
}

export const timeToRead = (html) => {
  let timeToRead = 0;
  const pureText = sanitizeHTML(html, { allowTags: [] });
  const avgWPM = 265;

  let latinChars = [];
  let cjChars = [];

  for (const char of pureText) {
    if (isCjChar(char)) {
      cjChars.push(char);
    } else {
      latinChars.push(char);
    }
  }

  // Multiply non-latin character string length by 0.56, because
  // on average one word consists of 2 characters in both Chinese and Japanese
  const wordCount = _.words(latinChars.join(``)).length + cjChars.length * 0.56;

  timeToRead = Math.round(wordCount / avgWPM);
  if (timeToRead === 0) {
    timeToRead = 1;
  }
  return timeToRead;
};

Прежде чем отправить запрос на вытяжение, я также добавил модульный тест для Timetoread Функция и проверен, если мой код отформатирован.

В следующий раз

В следующий раз я расскажу о другом вкладе в Гэтсби. В то же время, если вам понравилась статья, Следуй за мной в Твиттере Для получения дополнительных обновлений!

Оригинал: “https://dev.to/virtualkirill/contributing-to-gatsby-issue-21311-na0”