From a69dc8740a49bea0bf6c78bd9d304b8cf0316af3 Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Tue, 19 May 2026 15:45:53 +0530 Subject: [PATCH 1/5] Update content resize to use wordCountType for word count based on characters and words --- .../components/ContentResizingToolbar.tsx | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx index 85269060..253bc89c 100644 --- a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx +++ b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx @@ -16,10 +16,10 @@ import { import { useSelect, useDispatch } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState, useCallback, useMemo } from '@wordpress/element'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, _n, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { store as editorStore } from '@wordpress/editor'; -import { count } from '@wordpress/wordcount'; +import { count as wordCount, type Strategy } from '@wordpress/wordcount'; /** * Internal dependencies @@ -30,7 +30,7 @@ import { ICON_SHORTEN, ICON_EXPAND, ICON_REPHRASE } from '../icons'; import { ensureProvider } from '../../../utils/provider-status'; import AIIcon from '../../../../routes/ai-home/ai-icon'; -const SHORTEN_MIN_WORDS = 5; +const SHORTEN_MIN_WORDS_CHARACTERS = 5; const NOTICE_ID = 'ai_content_resizing_error'; /** @@ -73,6 +73,22 @@ export default function ContentResizingToolbar( { const blockEditorDispatch = useDispatch( blockEditorStore ) as any; const noticesDispatch = useDispatch( noticesStore ) as any; + /** + * translators: If your word count is based on single characters (e.g. East Asian characters), + * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. + * Do not translate into your own language. + * + * Uses the default (core) text domain so the word count type stays consistent + * with WordPress core's behavior. + * + * See - https://github.com/WordPress/ai/pull/577#discussion_r3265155502 + */ + // eslint-disable-next-line @wordpress/i18n-text-domain + const wordCountType = _x( + 'words', + 'Word count type. Do not translate!' + ) as Strategy; + const handleAction = useCallback( async ( action: ContentResizingAction ) => { if ( ! ensureProvider( NOTICE_ID ) ) { @@ -80,9 +96,13 @@ export default function ContentResizingToolbar( { } if ( action === 'shorten' ) { - const wordCount = count( blockContent, 'words', {} ); + const hasEnoughWords = wordCount( + blockContent, + wordCountType, + {} + ); // We need at least 5 words to shorten the content. - if ( wordCount < SHORTEN_MIN_WORDS ) { + if ( hasEnoughWords < SHORTEN_MIN_WORDS_CHARACTERS ) { noticesDispatch.createErrorNotice( __( 'Text is too short to shorten further.', 'ai' ), { @@ -125,7 +145,7 @@ export default function ContentResizingToolbar( { setIsLoading( false ); } }, - [ blockContent, noticesDispatch, postId ] + [ blockContent, noticesDispatch, postId, wordCountType ] ); const handleAccept = useCallback( () => { @@ -159,8 +179,8 @@ export default function ContentResizingToolbar( { } const delta = - count( suggestedContent, 'words', {} ) - - count( blockContent, 'words', {} ); + wordCount( suggestedContent, wordCountType, {} ) - + wordCount( blockContent, wordCountType, {} ); if ( delta === 0 ) { return { @@ -201,7 +221,7 @@ export default function ContentResizingToolbar( { magnitude ), }; - }, [ blockContent, suggestedContent ] ); + }, [ blockContent, suggestedContent, wordCountType ] ); const controls: Array< { title: string; From 923446f7af4224a3cd0e9ee5785edbf7686d42a0 Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 21 May 2026 15:29:24 +0530 Subject: [PATCH 2/5] Standardize wordCount across the experiments and added filter to add more counts --- .../Content_Classification.php | 9 ++- .../Content_Resizing/Content_Resizing.php | 5 +- .../Summarization/Summarization.php | 5 +- includes/helpers.php | 21 +++++++ .../components/useContentClassification.ts | 12 ++-- .../content-classification/types.ts | 1 + .../components/ContentResizingToolbar.tsx | 52 +++++++---------- src/experiments/content-resizing/types.ts | 8 +++ .../functions/useSummaryGeneration.ts | 25 ++++++--- src/experiments/summarization/types.ts | 8 +++ src/utils/word-count.ts | 56 +++++++++++++++++++ 11 files changed, 155 insertions(+), 47 deletions(-) create mode 100644 src/utils/word-count.ts diff --git a/includes/Experiments/Content_Classification/Content_Classification.php b/includes/Experiments/Content_Classification/Content_Classification.php index 38484578..356fb4ed 100644 --- a/includes/Experiments/Content_Classification/Content_Classification.php +++ b/includes/Experiments/Content_Classification/Content_Classification.php @@ -15,6 +15,8 @@ use WordPress\AI\Experiments\Experiment_Category; use WordPress\AI\Settings\Settings_Registration; +use function WordPress\AI\get_min_content_length; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -151,9 +153,10 @@ public function enqueue_assets( string $hook_suffix ): void { 'content_classification', 'ContentClassificationData', array( - 'enabled' => $this->is_enabled(), - 'strategy' => $this->get_strategy(), - 'maxSuggestions' => $this->get_max_suggestions(), + 'enabled' => $this->is_enabled(), + 'strategy' => $this->get_strategy(), + 'maxSuggestions' => $this->get_max_suggestions(), + 'minContentLength' => get_min_content_length( 'content-classification', 150 ), ) ); } diff --git a/includes/Experiments/Content_Resizing/Content_Resizing.php b/includes/Experiments/Content_Resizing/Content_Resizing.php index 6090dd23..e204bfbd 100644 --- a/includes/Experiments/Content_Resizing/Content_Resizing.php +++ b/includes/Experiments/Content_Resizing/Content_Resizing.php @@ -14,6 +14,8 @@ use WordPress\AI\Asset_Loader; use WordPress\AI\Experiments\Experiment_Category; +use function WordPress\AI\get_min_content_length; + // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; @@ -87,7 +89,8 @@ public function enqueue_assets( string $hook_suffix ): void { 'content_resizing', 'ContentResizingData', array( - 'enabled' => $this->is_enabled(), + 'enabled' => $this->is_enabled(), + 'minContentLength' => get_min_content_length( 'content-resizing', 5 ), ) ); } diff --git a/includes/Experiments/Summarization/Summarization.php b/includes/Experiments/Summarization/Summarization.php index 4255612e..3c9d70b7 100644 --- a/includes/Experiments/Summarization/Summarization.php +++ b/includes/Experiments/Summarization/Summarization.php @@ -14,6 +14,8 @@ use WordPress\AI\Asset_Loader; use WordPress\AI\Experiments\Experiment_Category; +use function WordPress\AI\get_min_content_length; + // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; @@ -106,10 +108,11 @@ public function enqueue_assets( string $hook_suffix ): void { * Filters the minimum content length required to enable summarization. * * @since 1.0.0 + * @deprecated x.x.x Use {@see 'wpai_min_content_length'} instead. * * @param int $min_content_length The minimum number of characters required. Default 100. */ - $min_content_length = (int) apply_filters( 'wpai_summarization_min_content_length', 100 ); + $min_content_length = (int) apply_filters( 'wpai_summarization_min_content_length', get_min_content_length( 'summarization', 100 ) ); Asset_Loader::localize_script( 'summarization', diff --git a/includes/helpers.php b/includes/helpers.php index 537d1ebe..b82cb09b 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -540,3 +540,24 @@ function is_connector_plugin_active( array $connector_data ): bool { return is_multisite() && function_exists( 'is_plugin_active_for_network' ) && is_plugin_active_for_network( $plugin_file ); } + +/** + * Returns the minimum content length required for a given feature. + * + * @since x.x.x + * + * @param string $feature_id The feature identifier (e.g. 'content-resizing', 'content-classification', 'summarization'). + * @param int $default The default minimum content length. + * @return int The minimum content length. + */ +function get_min_content_length( string $feature_id, int $default = 100 ): int { + /** + * Filters the minimum content length required for a feature. + * + * @since x.x.x + * + * @param int $min_content_length The minimum content length. Default 100. + * @param string $feature_id The feature identifier. + */ + return (int) apply_filters( 'wpai_min_content_length', $default, $feature_id ); +} diff --git a/src/experiments/content-classification/components/useContentClassification.ts b/src/experiments/content-classification/components/useContentClassification.ts index cb06e127..0227a021 100644 --- a/src/experiments/content-classification/components/useContentClassification.ts +++ b/src/experiments/content-classification/components/useContentClassification.ts @@ -10,7 +10,6 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; import { useState, useCallback } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; -import { count as wordCount } from '@wordpress/wordcount'; import { addQueryArgs } from '@wordpress/url'; import apiFetch from '@wordpress/api-fetch'; @@ -19,6 +18,7 @@ import apiFetch from '@wordpress/api-fetch'; */ import { runAbility } from '../../../utils/run-ability'; import { ensureProvider } from '../../../utils/provider-status'; +import { hasMinimumContent } from '../../../utils/word-count'; import type { ContentClassificationAbilityInput, ContentClassificationResponse, @@ -26,7 +26,7 @@ import type { ContentClassificationData, } from '../types'; -const MINIMUM_WORD_COUNT = 150; +const MINIMUM_CONTENT_COUNT_DEFAULT = 150; const NOTICE_ID = 'ai_content_classification_error'; const DEFAULT_MAX_SUGGESTIONS = 5; const MIN_SUGGESTIONS = 1; @@ -55,6 +55,8 @@ const getSettings = (): ContentClassificationData => { enabled: settings.enabled ?? false, strategy: settings.strategy ?? 'existing_only', maxSuggestions: normalizeMaxSuggestions( settings.maxSuggestions ), + minContentLength: + settings.minContentLength ?? MINIMUM_CONTENT_COUNT_DEFAULT, }; }; @@ -153,8 +155,10 @@ export function useContentClassification( taxonomy: string ): { const { removeNotice, createErrorNotice } = dispatch( noticesStore ) as any; // Check if content has enough words. - const hasEnoughContent = - wordCount( content || '', 'words' ) >= MINIMUM_WORD_COUNT; + const hasEnoughContent = hasMinimumContent( + content || '', + getSettings().minContentLength + ); const handleGenerate = useCallback( async () => { if ( ! ensureProvider( NOTICE_ID ) ) { diff --git a/src/experiments/content-classification/types.ts b/src/experiments/content-classification/types.ts index 8657b27e..079a840f 100644 --- a/src/experiments/content-classification/types.ts +++ b/src/experiments/content-classification/types.ts @@ -38,4 +38,5 @@ export interface ContentClassificationData { enabled: boolean; strategy: string; maxSuggestions: number; + minContentLength: number; } diff --git a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx index a7ea82e7..33c0fb01 100644 --- a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx +++ b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx @@ -16,24 +16,34 @@ import { import { useSelect, useDispatch } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState, useCallback, useMemo } from '@wordpress/element'; -import { __, _n, _x, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { store as editorStore } from '@wordpress/editor'; -import { count as wordCount, type Strategy } from '@wordpress/wordcount'; /** * Internal dependencies */ import { runAbility } from '../../../utils/run-ability'; import { getBlockText } from '../../../utils/blocks'; -import type { ContentResizingAction } from '../types'; +import type { ContentResizingAction, ContentResizingData } from '../types'; import { ICON_SHORTEN, ICON_EXPAND, ICON_REPHRASE } from '../icons'; import { ensureProvider } from '../../../utils/provider-status'; +import { getContentCount } from '../../../utils/word-count'; import AIIcon from '../../../../routes/ai-home/ai-icon'; -const SHORTEN_MIN_WORDS_CHARACTERS = 5; +const SHORTEN_MIN_CONTENT_LENGTH = 5; const NOTICE_ID = 'ai_content_resizing_error'; +const getSettings = (): ContentResizingData => { + const settings = ( window as any ).aiContentResizingData ?? {}; + + return { + enabled: settings.enabled ?? false, + minContentLength: + settings.minContentLength ?? SHORTEN_MIN_CONTENT_LENGTH, + }; +}; + /** * Content resizing toolbar component. * @@ -73,22 +83,6 @@ export default function ContentResizingToolbar( { const blockEditorDispatch = useDispatch( blockEditorStore ) as any; const noticesDispatch = useDispatch( noticesStore ) as any; - /** - * translators: If your word count is based on single characters (e.g. East Asian characters), - * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. - * Do not translate into your own language. - * - * Uses the default (core) text domain so the word count type stays consistent - * with WordPress core's behavior. - * - * See - https://github.com/WordPress/ai/pull/577#discussion_r3265155502 - */ - // eslint-disable-next-line @wordpress/i18n-text-domain - const wordCountType = _x( - 'words', - 'Word count type. Do not translate!' - ) as Strategy; - const handleAction = useCallback( async ( action: ContentResizingAction ) => { if ( ! ensureProvider( NOTICE_ID ) ) { @@ -96,13 +90,9 @@ export default function ContentResizingToolbar( { } if ( action === 'shorten' ) { - const hasEnoughWords = wordCount( - blockContent, - wordCountType, - {} - ); - // We need at least 5 words to shorten the content. - if ( hasEnoughWords < SHORTEN_MIN_WORDS_CHARACTERS ) { + const contentCount = getContentCount( blockContent ); + // We need at least the minimum content length to shorten. + if ( contentCount < getSettings().minContentLength ) { noticesDispatch.createErrorNotice( __( 'Text is too short to shorten further.', 'ai' ), { @@ -145,7 +135,7 @@ export default function ContentResizingToolbar( { setIsLoading( false ); } }, - [ blockContent, noticesDispatch, postId, wordCountType ] + [ blockContent, noticesDispatch, postId ] ); const handleAccept = useCallback( () => { @@ -179,8 +169,8 @@ export default function ContentResizingToolbar( { } const delta = - wordCount( suggestedContent, wordCountType, {} ) - - wordCount( blockContent, wordCountType, {} ); + getContentCount( suggestedContent ) - + getContentCount( blockContent ); if ( delta === 0 ) { return { @@ -221,7 +211,7 @@ export default function ContentResizingToolbar( { magnitude ), }; - }, [ blockContent, suggestedContent, wordCountType ] ); + }, [ blockContent, suggestedContent ] ); const controls: Array< { title: string; diff --git a/src/experiments/content-resizing/types.ts b/src/experiments/content-resizing/types.ts index ae6f68d7..167fd1f6 100644 --- a/src/experiments/content-resizing/types.ts +++ b/src/experiments/content-resizing/types.ts @@ -4,3 +4,11 @@ export interface ContentResizingAbilityInput { content: string; action: ContentResizingAction; } + +/** + * Localized data from the PHP side. + */ +export interface ContentResizingData { + enabled: boolean; + minContentLength: number; +} diff --git a/src/experiments/summarization/functions/useSummaryGeneration.ts b/src/experiments/summarization/functions/useSummaryGeneration.ts index 44583cb6..8bbaff16 100644 --- a/src/experiments/summarization/functions/useSummaryGeneration.ts +++ b/src/experiments/summarization/functions/useSummaryGeneration.ts @@ -18,10 +18,21 @@ import { store as noticesStore } from '@wordpress/notices'; */ import { generateSummary } from './generate-summary'; import { ensureProvider } from '../../../utils/provider-status'; -import { count } from '@wordpress/wordcount'; +import { hasMinimumContent } from '../../../utils/word-count'; +import type { SummarizationData } from '../types'; +const MINIMUM_CONTENT_COUNT_DEFAULT = 100; const NOTICE_ID = 'ai_summarization_error'; -const { aiSummarizationData } = window as any; + +const getSettings = (): SummarizationData => { + const settings = ( window as any ).aiSummarizationData ?? {}; + + return { + enabled: settings.enabled ?? false, + minContentLength: + settings.minContentLength ?? MINIMUM_CONTENT_COUNT_DEFAULT, + }; +}; /** * Summary generation hook. @@ -131,10 +142,10 @@ export function useSummaryGeneration() { }; // Minimum content length required for summarization. - const minContentLength: number = - aiSummarizationData?.minContentLength ?? 100; - const isContentTooShort = - count( content, 'characters_including_spaces' ) < minContentLength; + const isContentTooShort = ! hasMinimumContent( + content || '', + getSettings().minContentLength + ); return { isSummarizing, @@ -142,6 +153,6 @@ export function useSummaryGeneration() { summary, handleSummarize, isContentTooShort, - minContentLength, + minContentLength: getSettings().minContentLength, }; } diff --git a/src/experiments/summarization/types.ts b/src/experiments/summarization/types.ts index aefdc538..a279b14e 100644 --- a/src/experiments/summarization/types.ts +++ b/src/experiments/summarization/types.ts @@ -10,3 +10,11 @@ export interface SummarizationAbilityInput { context: string; [ key: string ]: string | undefined; } + +/** + * Localized data from the PHP side. + */ +export interface SummarizationData { + enabled: boolean; + minContentLength: number; +} diff --git a/src/utils/word-count.ts b/src/utils/word-count.ts new file mode 100644 index 00000000..dccfc475 --- /dev/null +++ b/src/utils/word-count.ts @@ -0,0 +1,56 @@ +/** + * Shared word count utilities. + * + * Provides a standardized way to count content length across all features, + * respecting the user's locale for word/character-based counting. + */ + +/** + * WordPress dependencies + */ +import { _x } from '@wordpress/i18n'; +import { count as wordCount, type Strategy } from '@wordpress/wordcount'; + +/** + * Returns the word count type based on the user's locale. + * + * Uses the default (core) text domain so the word count type stays consistent + * with WordPress core's behavior. + * + * @return {Strategy} The word count strategy. + */ +export function getWordCountType(): Strategy { + /* + * translators: If your word count is based on single characters (e.g. East Asian characters), + * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. + * Do not translate into your own language. + */ + // eslint-disable-next-line @wordpress/i18n-text-domain + return _x( 'words', 'Word count type. Do not translate!' ) as Strategy; +} + +/** + * Counts the content length using the locale-appropriate strategy. + * + * @param {string} content The content to count. + * + * @return {number} The content count (words or characters based on locale). + */ +export function getContentCount( content: string ): number { + return wordCount( content, getWordCountType() ); +} + +/** + * Checks if the content meets the minimum length requirement. + * + * @param {string} content The content to check. + * @param {number} minCount The minimum count required. + * + * @return {boolean} Whether the content meets the minimum length. + */ +export function hasMinimumContent( + content: string, + minCount: number +): boolean { + return getContentCount( content ) >= minCount; +} From 53ade0f31e58383789678722af20c1ed981b5166 Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 21 May 2026 16:08:05 +0530 Subject: [PATCH 3/5] Update text based on users locale and use exact min content length data --- .../components/SuggestionPanel.tsx | 23 ++++++-- .../components/useContentClassification.ts | 2 + .../components/ContentResizingToolbar.tsx | 59 ++++++++++++------- .../components/SummarizationPlugin.tsx | 27 ++++++--- 4 files changed, 78 insertions(+), 33 deletions(-) diff --git a/src/experiments/content-classification/components/SuggestionPanel.tsx b/src/experiments/content-classification/components/SuggestionPanel.tsx index 60f1ac23..87866f45 100644 --- a/src/experiments/content-classification/components/SuggestionPanel.tsx +++ b/src/experiments/content-classification/components/SuggestionPanel.tsx @@ -15,6 +15,7 @@ import { close as closeIcon, update } from '@wordpress/icons'; * Internal dependencies */ import { useContentClassification } from './useContentClassification'; +import { getWordCountType } from '../../../utils/word-count'; import type { TagSuggestion } from '../types'; interface SuggestionPanelProps { @@ -42,6 +43,7 @@ export default function SuggestionPanel( { handleAccept, handleDismiss, handleDismissAll, + minContentLength, } = useContentClassification( taxonomy ); const taxonomyObject: any = useSelect( @@ -76,10 +78,23 @@ export default function SuggestionPanel( { { ! hasEnoughContent && ! hasSuggestions && (

- { __( - 'Add more content to enable AI suggestions (approximately 150 words).', - 'ai' - ) } + { getWordCountType() !== 'words' + ? sprintf( + /* translators: %d: Minimum content length. */ + __( + 'Add more content to enable AI suggestions (approximately %d characters).', + 'ai' + ), + minContentLength + ) + : sprintf( + /* translators: %d: Minimum content length. */ + __( + 'Add more content to enable AI suggestions (approximately %d words).', + 'ai' + ), + minContentLength + ) }

) } diff --git a/src/experiments/content-classification/components/useContentClassification.ts b/src/experiments/content-classification/components/useContentClassification.ts index 0227a021..3a960661 100644 --- a/src/experiments/content-classification/components/useContentClassification.ts +++ b/src/experiments/content-classification/components/useContentClassification.ts @@ -141,6 +141,7 @@ export function useContentClassification( taxonomy: string ): { handleAccept: ( suggestion: TagSuggestion ) => void; handleDismiss: ( suggestion: TagSuggestion ) => void; handleDismissAll: () => void; + minContentLength: number; } { const { postId, content } = useSelect( ( selectFn ) => { const editor = selectFn( editorStore ); @@ -237,6 +238,7 @@ export function useContentClassification( taxonomy: string ): { handleAccept, handleDismiss, handleDismissAll, + minContentLength: getSettings().minContentLength, }; } diff --git a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx index 33c0fb01..c66c5dcd 100644 --- a/src/experiments/content-resizing/components/ContentResizingToolbar.tsx +++ b/src/experiments/content-resizing/components/ContentResizingToolbar.tsx @@ -28,7 +28,7 @@ import { getBlockText } from '../../../utils/blocks'; import type { ContentResizingAction, ContentResizingData } from '../types'; import { ICON_SHORTEN, ICON_EXPAND, ICON_REPHRASE } from '../icons'; import { ensureProvider } from '../../../utils/provider-status'; -import { getContentCount } from '../../../utils/word-count'; +import { getContentCount, getWordCountType } from '../../../utils/word-count'; import AIIcon from '../../../../routes/ai-home/ai-icon'; const SHORTEN_MIN_CONTENT_LENGTH = 5; @@ -168,6 +168,7 @@ export default function ContentResizingToolbar( { return null; } + const isCharacterType = getWordCountType() !== 'words'; const delta = getContentCount( suggestedContent ) - getContentCount( blockContent ); @@ -183,33 +184,49 @@ export default function ContentResizingToolbar( { const magnitude = Math.abs( delta ); if ( delta > 0 ) { + const label = isCharacterType + ? /* translators: %d: Number of characters added. */ + _n( '+%d character', '+%d characters', magnitude, 'ai' ) + : /* translators: %d: Number of words added. */ + _n( '+%d word', '+%d words', magnitude, 'ai' ); + const ariaLabel = isCharacterType + ? /* translators: %d: Number of characters added. */ + _n( + '%d character added', + '%d characters added', + magnitude, + 'ai' + ) + : /* translators: %d: Number of words added. */ + _n( '%d word added', '%d words added', magnitude, 'ai' ); + return { modifier: 'positive' as const, - label: sprintf( - /* translators: %d: Number of words added. */ - _n( '+%d word', '+%d words', magnitude, 'ai' ), - magnitude - ), - ariaLabel: sprintf( - /* translators: %d: Number of words added. */ - _n( '%d word added', '%d words added', magnitude, 'ai' ), - magnitude - ), + label: sprintf( label, magnitude ), + ariaLabel: sprintf( ariaLabel, magnitude ), }; } + const label = isCharacterType + ? /* translators: %d: Number of characters removed. */ + _n( '-%d character', '-%d characters', magnitude, 'ai' ) + : /* translators: %d: Number of words removed. */ + _n( '-%d word', '-%d words', magnitude, 'ai' ); + const ariaLabel = isCharacterType + ? /* translators: %d: Number of characters removed. */ + _n( + '%d character removed', + '%d characters removed', + magnitude, + 'ai' + ) + : /* translators: %d: Number of words removed. */ + _n( '%d word removed', '%d words removed', magnitude, 'ai' ); + return { modifier: 'negative' as const, - label: sprintf( - /* translators: %d: Number of words removed. */ - _n( '−%d word', '−%d words', magnitude, 'ai' ), - magnitude - ), - ariaLabel: sprintf( - /* translators: %d: Number of words removed. */ - _n( '%d word removed', '%d words removed', magnitude, 'ai' ), - magnitude - ), + label: sprintf( label, magnitude ), + ariaLabel: sprintf( ariaLabel, magnitude ), }; }, [ blockContent, suggestedContent ] ); diff --git a/src/experiments/summarization/components/SummarizationPlugin.tsx b/src/experiments/summarization/components/SummarizationPlugin.tsx index b5d8d114..a8367886 100644 --- a/src/experiments/summarization/components/SummarizationPlugin.tsx +++ b/src/experiments/summarization/components/SummarizationPlugin.tsx @@ -13,6 +13,7 @@ import { update } from '@wordpress/icons'; /** * Internal dependencies */ +import { getWordCountType } from '../../../utils/word-count'; import { useSummaryGeneration } from '../functions/useSummaryGeneration'; const { aiSummarizationData } = window as any; @@ -42,14 +43,24 @@ export default function SummarizationPlugin() { let buttonDescription: string; if ( isContentTooShort ) { - buttonDescription = sprintf( - /* translators: %d: minimum number of characters required */ - __( - 'Summarization will be available when the post content has at least %d characters.', - 'ai' - ), - minContentLength - ); + const isCharacterType = getWordCountType() !== 'words'; + buttonDescription = isCharacterType + ? sprintf( + /* translators: %d: minimum number of characters required */ + __( + 'Summarization will be available when the post content has at least %d characters.', + 'ai' + ), + minContentLength + ) + : sprintf( + /* translators: %d: minimum number of words required */ + __( + 'Summarization will be available when the post content has at least %d words.', + 'ai' + ), + minContentLength + ); } else if ( hasSummary ) { buttonDescription = __( 'This will update the generated summary block with a new summary of the content of this post.', From 72f53df4c7b3d317a5f3cb7e9a9fe85f1c308edf Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 21 May 2026 16:10:29 +0530 Subject: [PATCH 4/5] Fix phpcs errors --- includes/helpers.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/helpers.php b/includes/helpers.php index b82cb09b..d8425038 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -546,18 +546,18 @@ function is_connector_plugin_active( array $connector_data ): bool { * * @since x.x.x * - * @param string $feature_id The feature identifier (e.g. 'content-resizing', 'content-classification', 'summarization'). - * @param int $default The default minimum content length. + * @param string $feature_id The feature identifier (e.g. 'content-resizing', 'content-classification', 'summarization'). + * @param int $content_length The default minimum content length. * @return int The minimum content length. */ -function get_min_content_length( string $feature_id, int $default = 100 ): int { +function get_min_content_length( string $feature_id, int $content_length = 100 ): int { /** * Filters the minimum content length required for a feature. * * @since x.x.x * - * @param int $min_content_length The minimum content length. Default 100. - * @param string $feature_id The feature identifier. + * @param int $content_length The minimum content length. Default 100. + * @param string $feature_id The feature identifier. */ - return (int) apply_filters( 'wpai_min_content_length', $default, $feature_id ); + return (int) apply_filters( 'wpai_min_content_length', $content_length, $feature_id ); } From 3e0143c1c23cb38e046612ad47e56b028dee8380 Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 21 May 2026 16:40:08 +0530 Subject: [PATCH 5/5] Fix e2e tests for content summarization --- .../experiments/content-summarization.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/e2e/specs/experiments/content-summarization.spec.js b/tests/e2e/specs/experiments/content-summarization.spec.js index 51c46605..9d782408 100644 --- a/tests/e2e/specs/experiments/content-summarization.spec.js +++ b/tests/e2e/specs/experiments/content-summarization.spec.js @@ -36,12 +36,12 @@ test.describe( 'Content Summarization Experiment', () => { // Enable the Content Summarization Experiment. await enableExperiment( admin, page, 'Content Summarization' ); - // Create a new post with content that meets the minimum length requirement (>= 100 chars). + // Create a new post with content that meets the minimum length requirement (>= 100 words). await admin.createNewPost( { postType: 'post', title: 'Test Content Summarization Experiment', content: - 'This is some test content for the Content Summarization Experiment. It needs to be at least one hundred characters long.', + 'This is some test content for the Content Summarization Experiment. It needs to have enough words to meet the minimum content length requirement for summarization to be enabled. The summarization feature requires a substantial amount of text before it will allow the user to generate a summary of the post content. This ensures that the generated summary is meaningful and provides value to readers who want a quick overview of what the full article contains. Adding more words here to make sure we exceed the minimum threshold that is configured for this experiment in the plugin settings and server side filters.', } ); // Save the post. @@ -132,7 +132,7 @@ test.describe( 'Content Summarization Experiment', () => { // Enable the Content Summarization Experiment. await enableExperiment( admin, page, 'Content Summarization' ); - // Create a new post with content shorter than 100 characters. + // Create a new post with content shorter than 100 words. await admin.createNewPost( { postType: 'post', title: 'Test Short Content', @@ -156,7 +156,7 @@ test.describe( 'Content Summarization Experiment', () => { // The descriptive text should explain when the button will be enabled. await expect( page.locator( '.ai-summarization-plugin-container .description' ) - ).toContainText( '100 characters' ); + ).toContainText( '100 words' ); } ); test( 'Summarize button is enabled when content meets the minimum length', async ( { @@ -170,12 +170,12 @@ test.describe( 'Content Summarization Experiment', () => { // Enable the Content Summarization Experiment. await enableExperiment( admin, page, 'Content Summarization' ); - // Create a new post with content that is at least 100 characters. + // Create a new post with content that is at least 100 words. await admin.createNewPost( { postType: 'post', title: 'Test Sufficient Content', content: - 'This post has enough content to meet the minimum character requirement for the summarization feature to be enabled.', + 'This post has enough content to meet the minimum word count requirement for the summarization feature to be enabled. The content needs to contain at least one hundred words so that the summarization experiment can generate a meaningful summary of the text. By including multiple sentences with various topics and ideas, we ensure that the AI has sufficient material to work with when creating a concise overview. This paragraph continues to add more words to reach the necessary threshold for testing purposes and to verify the feature works correctly.', } ); // Save the post. @@ -192,10 +192,10 @@ test.describe( 'Content Summarization Experiment', () => { await expect( generateButton ).toBeVisible(); await expect( generateButton ).toBeEnabled(); - // The descriptive text should NOT mention the minimum character requirement. + // The descriptive text should NOT mention the minimum word requirement. await expect( page.locator( '.ai-summarization-plugin-container .description' ) - ).not.toContainText( 'characters' ); + ).not.toContainText( 'words' ); } ); test( 'Ensure the Content Summarization Experiment UI is not visible when the experiment is disabled', async ( {