resolve_language_sample( $user_message, $history ); $locale_hint = $this->resolve_locale_hint( $user_id ); if ( $sample !== '' ) { return sprintf( /* translators: 1: learner message sample, 2: locale hint. */ __( 'Language policy: reply strictly in the same language as the learner. Do not default to English. Learner sample: "%1$s". Locale fallback: %2$s.', 'learnpress' ), $sample, $locale_hint !== '' ? $locale_hint : 'n/a' ); } return sprintf( /* translators: %s: locale hint. */ __( 'Language policy: reply strictly in the learner language and do not default to English. If the latest learner message is language-neutral, use locale fallback: %s.', 'learnpress' ), $locale_hint !== '' ? $locale_hint : 'n/a' ); } // ---------------------------------------------------------------- // Private helpers // ---------------------------------------------------------------- /** * Pick the best text sample to infer learner language. * * Prefers the current message; falls back to previous user turns. * * @param string $user_message Current learner input. * @param array $history Conversation history. * * @return string */ private function resolve_language_sample( string $user_message, array $history ): string { $current = $this->trim_text_for_prompt( $user_message ); if ( $this->has_letters( $current ) && ! $this->is_language_neutral_command( $current ) ) { return $current; } for ( $index = count( $history ) - 1; $index >= 0; $index-- ) { $item = $history[ $index ] ?? array(); if ( ( $item['role'] ?? '' ) !== 'user' || ! isset( $item['content'] ) ) { continue; } $content = $this->trim_text_for_prompt( (string) $item['content'] ); if ( ! $this->has_letters( $content ) || $this->is_language_neutral_command( $content ) ) { continue; } return $content; } return ''; } /** * Resolve locale hint from WordPress user/site settings. * * @param int $user_id Current user ID. * * @return string */ private function resolve_locale_hint( int $user_id ): string { $locale = (string) get_user_locale( $user_id ); if ( $locale !== '' ) { return $locale; } $locale = (string) determine_locale(); if ( $locale !== '' ) { return $locale; } return (string) get_locale(); } /** * Determine if input is a language-neutral command (e.g. /quick-quiz, "1", "A", ...). * * @param string $text Input text. * * @return bool */ private function is_language_neutral_command( string $text ): bool { $normalized = trim( $text ); if ( $normalized === '' ) { return true; } if ( str_starts_with( $normalized, '/' ) ) { return true; } return preg_match( '/^[\d\W_]+$/u', $normalized ) === 1; } /** * Check whether input contains at least one letter character. * * @param string $text Input text. * * @return bool */ private function has_letters( string $text ): bool { return preg_match( '/\p{L}/u', $text ) === 1; } /** * Trim and cap text length for safe system prompt injection. * * @param string $text Input text. * * @return string */ private function trim_text_for_prompt( string $text ): string { $clean = trim( preg_replace( '/\s+/', ' ', $text ) ?? '' ); if ( $clean === '' ) { return ''; } return mb_substr( $clean, 0, 220, 'UTF-8' ); } }