Repository: wagtail/wagtail
File: client/src/includes/contentMetrics.ts (TypeScript)

Code before:
  const longWordCount = words.reduce((count, segment) => {
    if (segment.isWordLike && segment.segment.length > 6) {
      return count + 1;
    }
    return count;
  }, 0);

  // If no sentences found, return "Poor"
  if (sentenceCount === 0 || wordCount === 0) {
    return gettext('Good');
  }

  // LIX formula: (A / B) + (C * 100 / A)
  // A = number of words, B = number of sentences, C = number of long words
  const avgWordsPerSentence = wordCount / sentenceCount;
  const percentLongWords = (longWordCount / wordCount) * 100;
  const lixScore = avgWordsPerSentence + percentLongWords;

  // Convert numerical score to translated label
  if (lixScore < 30) {
    return gettext('Good');
  } else if (lixScore < 40) {
    return gettext('Fair');
  }

  return gettext('Poor');
};

export interface ContentMetrics {
  wordCount: number;
  readingTime: number;
  readabilityScore: string;
}

/** Options for extracting page content from the preview iframe. */
export interface ContentExtractorOptions {
  /**
   * The CSS selector for the target element to extract content from.
   * If not provided, or if the target element is not found, the entire document
   * body will be used.
   */
  targetElement: string;
}

/** The extracted content from the preview iframe. */
export interface ExtractedContent {
  /** The language of the preview iframe's document. */
  lang: string;
  /** The text-only content of the target element. */
  innerText: string;
  /** The HTML content of the target element. */

Review comment (refactor):
I think it would be useful to split this into two  separate functions: one for getting the LIX score, and one for determining the actual label we use for display. It would make it easier to support different score ranges for different languages, and people interested in the value can listen to the `w-preview:content` event and get it from `event.detail.metrics`.

Code after:
  for (const { segment, isWordLike } of wordSegmenter.segment(text)) {
    if (isWordLike) {
      words += 1;
      if (segment.length > 6) {
        longWords += 1;
      }
    }
  }

  // If 0 or too little content is found, return 0.
  // Readability scoring is not relevant on very short content.
  if (sentences === 0 || words < 10) {
    return 0;
  }

  const avgWordsPerSentence = words / sentences;
  const percentLongWords = (longWords / words) * 100;

  return avgWordsPerSentence + percentLongWords;
};

/**
 * Lenient interpretation of standard LIX score ranges.
 */
export const getReadabilityScore = (lixScore: number): string => {
  if (lixScore < 35) {
    return gettext('Good');
  }
  if (lixScore < 45) {
    // Translators: denotes the readability of an average text.
    return gettext('Fair');
  }

  // Avoid a direct value judgement on the worst scores.
  // Translators: denotes the readability of a difficult text.
  return gettext('Complex');
};

export interface ContentMetrics {
  wordCount: number;
  readingTime: number;
  readabilityScore: string;
}

/** Options for extracting page content from the preview iframe. */
export interface ContentExtractorOptions {
  /**
   * The CSS selector for the target element to extract content from.
   * If not provided, or if the target element is not found, the entire document
   * body will be used.
   */