diff --git a/docs/brainstorming/template-compile.md b/docs/brainstorming/template-compile.md
new file mode 100644
index 0000000000000..908c95a82138f
--- /dev/null
+++ b/docs/brainstorming/template-compile.md
@@ -0,0 +1,21 @@
+# Template Compile Mode
+
+**Date:** 2026-02-04
+**Status:** Brainstorming
+
+---
+
+## Two Modes of Template Usage
+
+### 1. Flush Mode (current)
+- One-shot rendering, like `::sprintf`
+- Parse and render in a single pass
+
+### 2. Compile Mode (new idea)
+- A `compile()` method that:
+ - Captures placeholder offsets
+ - Returns a compiled/prepared template
+ - Accepts an array of replacements at render time
+ - Can be rendered multiple times efficiently from a single compilation
+
+**Use case:** When you need to render the same template structure repeatedly with different data, avoid re-parsing the template each time.
diff --git a/docs/brainstorming/template-html-context.md b/docs/brainstorming/template-html-context.md
new file mode 100644
index 0000000000000..c0691b0913335
--- /dev/null
+++ b/docs/brainstorming/template-html-context.md
@@ -0,0 +1,17 @@
+# Template HTML Context
+
+**Date:** 2026-02-04
+**Status:** Brainstorming
+
+---
+
+## HTML Context Constraints
+
+### Problem
+The HTML processor cannot normalize certain templates like `
` because `
` cannot exist outside of a `
` element. Context matters for valid HTML.
+
+### Potential Solutions
+- Use HTML processor instead of TAG processor
+- Add private methods for rendering nested templates
+- Processor creates a **fragment parser** at the template replacement location
+ - Fragment parser handles the child template with proper parent context
diff --git a/docs/plans/2026-02-04-template-api-redesign-v2.md b/docs/plans/2026-02-04-template-api-redesign-v2.md
new file mode 100644
index 0000000000000..75621fb65d1fe
--- /dev/null
+++ b/docs/plans/2026-02-04-template-api-redesign-v2.md
@@ -0,0 +1,125 @@
+# WP_HTML_Template API Redesign v2
+
+**Date:** 2026-02-04
+**Status:** Design Complete
+
+---
+
+## Overview
+
+A single-class template API with strict error handling. Parse once, bind values, render on demand.
+
+## Essential Interface
+
+```php
+class WP_HTML_Template {
+ public static function from(string $template): static;
+ public function bind(array $replacements): static;
+ public function render(): string|false;
+}
+```
+
+### Methods
+
+**`from(string $template): static`**
+
+Creates a template from a string.
+
+**`bind(array $replacements): static`**
+
+Returns a new immutable instance with replacements bound.
+
+**`render(): string|false`**
+
+Renders the template to an HTML string. Returns `false` on any error.
+
+## Usage Patterns
+
+### One-shot rendering
+
+```php
+$html = WP_HTML_Template::from('
Hello, %name>!
')
+ ->bind(['name' => 'World'])
+ ->render();
+```
+
+### Nested templates (HTML injection)
+
+A template instance can be used as a replacement value. It will be rendered and injected as HTML (not escaped).
+
+```php
+$html = WP_HTML_Template::from('
%icon> %message>
')
+ ->bind([
+ 'icon' => WP_HTML_Template::from(''),
+ 'message' => 'Something went wrong.',
+ ])
+ ->render();
+```
+
+### Nested templates with their own placeholders
+
+```php
+$html = WP_HTML_Template::from('
%content>
')
+ ->bind([
+ 'content' => WP_HTML_Template::from('%text>')
+ ->bind(['url' => '/help', 'text' => 'Learn more']),
+ ])
+ ->render();
+```
+
+### Loop rendering
+
+Parse once, bind and render with different values each iteration.
+
+```php
+$item_template = WP_HTML_Template::from('
%item>
');
+
+foreach ($items as $item) {
+ echo $item_template->bind(['item' => $item])->render();
+}
+```
+
+## Replacement Values
+
+| Replacement Type | Text Context | Attribute Context |
+|------------------|--------------|-------------------|
+| String | Escaped | Escaped |
+| WP_HTML_Template | Rendered (HTML preserved) | `false` (error) |
+
+## Error Handling
+
+All error conditions cause `render()` to return `false`:
+
+| Condition | Result |
+|-----------|--------|
+| Missing replacement key | `false` |
+| Unused replacement key | `false` |
+| Template in attribute context | `false` |
+| HTML processing/normalization failure | `false` |
+
+Replacements must match placeholders exactly—no more, no less.
+
+## Immutability
+
+`bind()` always returns a new instance. Templates are safe to reuse:
+
+```php
+$tpl = WP_HTML_Template::from('
');
+$t->bind(['class' => $otherTemplate->bind([...])]);
+// Warning: Replacement 'class' must be a string (attribute context)
+```
+
+`render()` returns `false`.
+
+## Migration from Current API
+
+| Old | New |
+|-----|-----|
+| `T::sprintf($tpl, $r)` | `T::render($tpl, $r)` |
+| `T::from($tpl, $r)->render()` | `T::from($tpl)->bind($r)->render()` |
+| `T::from($tpl)->render($r)` | `T::from($tpl)->bind($r)->render()` |
+
+Breaking changes are acceptable since this is pre-release code (`@since 7.0.0`).
diff --git a/docs/plans/2026-02-06-unified-edits-array-design.md b/docs/plans/2026-02-06-unified-edits-array-design.md
new file mode 100644
index 0000000000000..c5752fd227061
--- /dev/null
+++ b/docs/plans/2026-02-06-unified-edits-array-design.md
@@ -0,0 +1,106 @@
+# Unified Edits Array Design
+
+## Problem
+
+`WP_HTML_Template` currently uses three separate arrays to track compiled data:
+
+- `$compiled` — placeholder offsets, keyed by name, with context
+- `$text_normalizations` — `[start, length, normalized_text]` tuples
+- `$attr_escapes` — `[start, length]` tuples (computed at render time)
+
+These serve similar purposes (tracking spans to replace) but have different shapes and processing logic. The render method builds an intermediate `$updates` array from all three, sorts it, then applies replacements.
+
+Additionally, `attr_escapes` defers computation to render time unnecessarily — the template string is immutable, so decode→re-encode can happen at compile time.
+
+## Design
+
+Replace all three arrays with two:
+
+### `$edits` — unified edit list
+
+A flat array of edit operations, naturally ordered by ascending offset (the processor walks the document linearly). Each entry is one of:
+
+**Pre-computed replacement** (normalizations and escapes):
+```php
+['start' => 100, 'length' => 5, 'replacement' => '&']
+```
+
+**Placeholder reference** (needs render-time lookup):
+```php
+['start' => 200, 'length' => 12, 'placeholder' => 'username', 'context' => 'text']
+```
+
+The `context` field is `'text'` or `'attribute'`, used to validate that templates aren't inserted into attribute values.
+
+### `$placeholder_names` — validation index
+
+A set of placeholder names for O(1) lookup during `bind()`:
+
+```php
+['username' => true, 'title' => true]
+```
+
+This replaces the current pattern of iterating `$compiled` to build a lookup on every `bind()` call.
+
+## Compile-time changes
+
+1. **Pre-compute attribute escapes**: Move the decode→re-encode logic from `render()` to `compile()`. Store only if the result differs from the original span (same redundancy check as text normalizations).
+
+2. **Single array population**: As the processor encounters normalizations, escapes, or placeholders, append to `$edits` in document order.
+
+3. **Build placeholder index**: When encountering a placeholder, also add its name to `$placeholder_names`.
+
+## Render-time changes
+
+The render loop becomes a single reverse iteration:
+
+```php
+foreach (array_reverse($this->edits) as $edit) {
+ if (isset($edit['placeholder'])) {
+ // Look up replacement value
+ // Validate template-in-attribute
+ // Escape and apply
+ } else {
+ // Pre-computed, apply directly
+ $html = substr_replace($html, $edit['replacement'], $edit['start'], $edit['length']);
+ }
+}
+```
+
+No intermediate `$updates` array. No `usort()`.
+
+## Bind-time changes
+
+Validation uses `$placeholder_names` directly instead of building a lookup:
+
+```php
+// Check for missing keys
+foreach ($this->placeholder_names as $name => $_) {
+ // ... validate $name exists in $replacements
+}
+
+// Check for unused keys
+foreach ($replacements as $key => $_) {
+ // ... validate $key exists in $placeholder_names
+}
+```
+
+## Invariants
+
+- Offsets in `$edits` are strictly ascending (document order)
+- No overlapping spans
+- Entries have either `replacement` (pre-computed) or `placeholder` + `context` (render-time)
+- Every name in an edit with `placeholder` key exists in `$placeholder_names`
+
+## Trade-offs
+
+**Gains:**
+- Three properties → two properties
+- Render: three foreach loops + sort → one reverse iteration
+- Attribute escapes pre-computed (less render-time work)
+- Redundant escapes filtered out (fewer edits when template is well-formed)
+- Clearer mental model: "edits" are things to replace, "placeholder_names" is the validation index
+
+**Costs:**
+- Slightly more memory per placeholder entry (storing `context` per offset instead of once per name)
+- Loses grouping by placeholder name (negligible — hash lookups are cheap)
diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php
index 69e3e5d2c7557..214e06502260a 100644
--- a/src/wp-includes/html-api/class-wp-html-tag-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php
@@ -708,7 +708,7 @@ class WP_HTML_Tag_Processor {
* @since 6.2.0
* @var WP_HTML_Attribute_Token[]
*/
- private $attributes = array();
+ protected $attributes = array();
/**
* Tracks spans of duplicate attributes on a given tag, used for removing
diff --git a/src/wp-includes/html-api/class-wp-html-template.php b/src/wp-includes/html-api/class-wp-html-template.php
new file mode 100644
index 0000000000000..033ea88e83a77
--- /dev/null
+++ b/src/wp-includes/html-api/class-wp-html-template.php
@@ -0,0 +1,470 @@
+ int, 'length' => int, 'replacement' => string]
+ *
+ * Placeholder reference (render-time lookup):
+ * ['start' => int, 'length' => int, 'placeholder' => string, 'context' => 'text'|'attribute']
+ *
+ * @since 7.0.0
+ * @var array
+ */
+ private array $edits = array();
+
+ /**
+ * Placeholder names for O(1) validation.
+ *
+ * @since 7.0.0
+ * @var array
+ */
+ private array $placeholder_names = array();
+
+ /**
+ * Returns the compiled placeholder metadata.
+ *
+ * Derives the grouped structure from $edits on-demand.
+ * If a placeholder appears in both text and attribute contexts,
+ * the attribute context takes precedence (more restrictive).
+ *
+ * @since 7.0.0
+ *
+ * @return array Associative array of placeholder_name => metadata.
+ */
+ public function get_placeholders(): array {
+ $this->compile();
+
+ $result = array();
+
+ foreach ( $this->edits as $edit ) {
+ if ( ! isset( $edit['placeholder'] ) ) {
+ continue;
+ }
+
+ $placeholder = $edit['placeholder'];
+ $context = $edit['context'];
+
+ if ( ! isset( $result[ $placeholder ] ) ) {
+ $result[ $placeholder ] = array(
+ 'offsets' => array(),
+ 'context' => $context,
+ );
+ } else {
+ // Promote text context to attribute context.
+ if ( 'attribute' === $context ) {
+ $result[ $placeholder ]['context'] = 'attribute';
+ }
+ }
+
+ $result[ $placeholder ]['offsets'][] = array( $edit['start'], $edit['length'] );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Compiles the template to extract placeholder metadata.
+ *
+ * Parses the template once and caches placeholder positions, lengths,
+ * and contexts. If a placeholder appears in both text and attribute
+ * contexts, the attribute context takes precedence (more restrictive).
+ *
+ * @since 7.0.0
+ */
+ private function compile(): void {
+ if ( $this->is_compiled ) {
+ return;
+ }
+
+ $this->is_compiled = true;
+ $this->edits = array();
+ $this->placeholder_names = array();
+
+ $processor = ( new class( '', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE ) extends WP_HTML_Processor {
+ public function get_html(): string {
+ return $this->html;
+ }
+
+ public function get_bookmark( string $name ) {
+ return $this->bookmarks[ "_{$name}" ] ?? null;
+ }
+
+ public function get_tag_attributes(): array {
+ return $this->attributes;
+ }
+ } )::create_fragment( $this->template_string );
+
+ if ( null === $processor ) {
+ return;
+ }
+
+ while ( $processor->next_token() ) {
+ switch ( $processor->get_token_type() ) {
+ /*
+ * Track text normalizations to prevent something like
+ * `a<%tag-name>u` from becoming `au` after replacement.
+ */
+ case '#text':
+ $processor->set_bookmark( 'text' );
+ $mark = $processor->get_bookmark( 'text' );
+ if ( null === $mark ) {
+ break;
+ }
+ $normalized = $processor->serialize_token();
+ if ( 0 !== substr_compare( $processor->get_html(), $normalized, $mark->start, $mark->length ) ) {
+ $this->edits[] = array(
+ 'start' => $mark->start,
+ 'length' => $mark->length,
+ 'replacement' => $normalized,
+ );
+ }
+ break;
+
+ case '#funky-comment':
+ $processor->set_bookmark( 'placeholder' );
+ $mark = $processor->get_bookmark( 'placeholder' );
+ if ( null === $mark ) {
+ break;
+ }
+
+ $start = $mark->start;
+ $length = $mark->length;
+ $html = $processor->get_html();
+
+ // Must be at least `%x>` (5 chars) and start with `%`
+ if ( $length < 5 || '%' !== $html[ $start + 2 ] ) {
+ break;
+ }
+
+ $placeholder = trim( substr( $html, $start + 3, $length - 4 ), " \t\n\r\f" );
+
+ // Valid placeholders match `/[a-z0-9_-]+/i`
+ if ( strlen( $placeholder ) !== strspn( $placeholder, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-' ) ) {
+ break;
+ }
+
+ // New: append placeholder edit and register name.
+ $this->edits[] = array(
+ 'start' => $start,
+ 'length' => $length,
+ 'placeholder' => $placeholder,
+ 'context' => 'text',
+ );
+ $this->placeholder_names[ $placeholder ] = true;
+ break;
+
+ case '#tag':
+ if ( $processor->is_tag_closer() ) {
+ break;
+ }
+
+ $html = $processor->get_html();
+ foreach ( $processor->get_tag_attributes() as $attribute ) {
+ // Boolean attributes cannot contain placeholders.
+ if ( $attribute->is_true ) {
+ continue;
+ }
+ // At least `%x>` to contain a placeholder.
+ if ( $attribute->value_length < 5 ) {
+ continue;
+ }
+
+ $last_offset = $attribute->value_starts_at;
+ $offset = $attribute->value_starts_at;
+ $end = $offset + $attribute->value_length;
+
+ while (
+ 1 === preg_match(
+ '#%[ \\t\\r\\f\\n]*([a-z0-9_-]+)[ \\t\\r\\f\\n]*>#i',
+ $html,
+ $matches,
+ PREG_OFFSET_CAPTURE,
+ $offset
+ )
+ && $matches[0][1] < $end
+ ) {
+ $placeholder = $matches[1][0];
+ $match_start = $matches[0][1];
+ $match_length = strlen( $matches[0][0] );
+
+ // Pre-compute escape for text segment before this placeholder.
+ if ( $match_start > $last_offset ) {
+ $seg_length = $match_start - $last_offset;
+ $original = substr( $html, $last_offset, $seg_length );
+ $decoded = WP_HTML_Decoder::decode_attribute( $original );
+ $escaped = strtr(
+ $decoded,
+ array(
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ "'" => ''',
+ '"' => '"',
+ )
+ );
+ // Only add edit if escaping actually changes the text.
+ if ( $escaped !== $original ) {
+ $this->edits[] = array(
+ 'start' => $last_offset,
+ 'length' => $seg_length,
+ 'replacement' => $escaped,
+ );
+ }
+ }
+
+ // New: append placeholder edit and register name.
+ $this->edits[] = array(
+ 'start' => $match_start,
+ 'length' => $match_length,
+ 'placeholder' => $placeholder,
+ 'context' => 'attribute',
+ );
+ $this->placeholder_names[ $placeholder ] = true;
+
+ $last_offset = $match_start + $match_length;
+ $offset = $last_offset;
+ }
+
+ // Pre-compute escape for trailing text segment after last placeholder.
+ if ( $last_offset < $end ) {
+ $seg_length = $end - $last_offset;
+ $original = substr( $html, $last_offset, $seg_length );
+ $decoded = WP_HTML_Decoder::decode_attribute( $original );
+ $escaped = strtr(
+ $decoded,
+ array(
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ "'" => ''',
+ '"' => '"',
+ )
+ );
+ // Only add edit if escaping actually changes the text.
+ if ( $escaped !== $original ) {
+ $this->edits[] = array(
+ 'start' => $last_offset,
+ 'length' => $seg_length,
+ 'replacement' => $escaped,
+ );
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private function __construct( string $template_string, array $replacements ) {
+ $this->template_string = $template_string;
+ $this->replacements = $replacements;
+ }
+
+ /**
+ * Creates a template from a string.
+ *
+ * @since 7.0.0
+ *
+ * @param string $template The template string with placeholders.
+ * @return static The template instance.
+ */
+ public static function from( string $template ): static {
+ return new static( $template, array() );
+ }
+
+ /**
+ * Returns a new immutable instance with replacements bound.
+ *
+ * Triggers compilation if not already done. Validates replacements:
+ * - Warns if a placeholder has no corresponding replacement
+ * - Warns if a replacement key has no corresponding placeholder
+ * - Warns if a template is used in attribute context
+ *
+ * @since 7.0.0
+ *
+ * @param array $replacements The replacement values.
+ * @return static A new template instance with the replacements bound.
+ */
+ public function bind( array $replacements ): static {
+ $this->compile();
+
+ // Check for missing keys (placeholder without replacement).
+ foreach ( $this->placeholder_names as $placeholder => $_ ) {
+ $placeholder = (string) $placeholder;
+ $found = array_key_exists( $placeholder, $replacements );
+ if ( ! $found && ctype_digit( $placeholder ) ) {
+ $found = array_key_exists( (int) $placeholder, $replacements );
+ }
+ if ( ! $found ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ 'Missing replacement for placeholder: %s',
+ $placeholder
+ ),
+ '7.0.0'
+ );
+ }
+ }
+
+ // Check for unused keys (replacement without placeholder).
+ foreach ( $replacements as $key => $value ) {
+ $str_key = (string) $key;
+ $found = isset( $this->placeholder_names[ $key ] ) || isset( $this->placeholder_names[ $str_key ] );
+ if ( ! $found ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ 'Unused replacement key: %s',
+ $key
+ ),
+ '7.0.0'
+ );
+ }
+ }
+
+ // Check for templates in attribute context.
+ foreach ( $this->edits as $edit ) {
+ if ( ! isset( $edit['placeholder'] ) || 'attribute' !== $edit['context'] ) {
+ continue;
+ }
+
+ $placeholder = $edit['placeholder'];
+ $key = ctype_digit( $placeholder ) ? (int) $placeholder : $placeholder;
+ $value = $replacements[ $key ] ?? $replacements[ $placeholder ] ?? null;
+
+ if ( $value instanceof self ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ 'Template cannot be used in attribute context: %s',
+ $placeholder
+ ),
+ '7.0.0'
+ );
+ break; // Only warn once per placeholder name.
+ }
+ }
+
+ $new = new static( $this->template_string, $replacements );
+ $new->is_compiled = $this->is_compiled;
+ $new->edits = $this->edits;
+ $new->placeholder_names = $this->placeholder_names;
+ return $new;
+ }
+
+ /**
+ * Renders the template to an HTML string.
+ *
+ * Uses pre-compiled placeholder metadata to perform replacements
+ * without re-parsing. Collects all updates (placeholder replacements,
+ * text normalizations, attribute text escaping) and applies them
+ * from end to start using substr_replace() to preserve positions.
+ *
+ * Returns false on any error:
+ * - Missing replacement key (placeholder without corresponding replacement)
+ * - Unused replacement key (replacement without corresponding placeholder)
+ * - Template in attribute context
+ * - HTML processing/normalization failure
+ *
+ * @since 7.0.0
+ *
+ * @return string|false The rendered HTML, or false on error.
+ */
+ public function render(): string|false {
+ $this->compile();
+
+ if ( empty( $this->replacements ) ) {
+ return WP_HTML_Processor::normalize( $this->template_string ) ?? $this->template_string;
+ }
+
+ $escape_map = array(
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ "'" => ''',
+ '"' => '"',
+ );
+
+ $html = $this->template_string;
+ $used_keys = array();
+
+ // Process edits in reverse order (end to start) to preserve positions.
+ foreach ( array_reverse( $this->edits ) as $edit ) {
+ if ( isset( $edit['placeholder'] ) ) {
+ // Placeholder: look up replacement value.
+ $placeholder = $edit['placeholder'];
+
+ if ( array_key_exists( $placeholder, $this->replacements ) ) {
+ $key = $placeholder;
+ } elseif ( ctype_digit( $placeholder ) && array_key_exists( (int) $placeholder, $this->replacements ) ) {
+ $key = (int) $placeholder;
+ } else {
+ return false;
+ }
+
+ $used_keys[ $key ] = true;
+ $value = $this->replacements[ $key ];
+
+ if ( $value instanceof self ) {
+ if ( 'attribute' === $edit['context'] ) {
+ return false;
+ }
+
+ $rendered = $value->render();
+ if ( false === $rendered ) {
+ return false;
+ }
+
+ $html = substr_replace( $html, $rendered, $edit['start'], $edit['length'] );
+ } elseif ( is_string( $value ) ) {
+ $escaped = strtr( $value, $escape_map );
+ $html = substr_replace( $html, $escaped, $edit['start'], $edit['length'] );
+ } else {
+ return false;
+ }
+ } else {
+ // Pre-computed replacement: apply directly.
+ $html = substr_replace( $html, $edit['replacement'], $edit['start'], $edit['length'] );
+ }
+ }
+
+ // Return false if any replacement key was not used.
+ if ( count( $used_keys ) !== count( $this->replacements ) ) {
+ return false;
+ }
+
+ return WP_HTML_Processor::normalize( $html ) ?? $html;
+ }
+}
diff --git a/src/wp-settings.php b/src/wp-settings.php
index 60c220100f539..b80167116f541 100644
--- a/src/wp-settings.php
+++ b/src/wp-settings.php
@@ -276,6 +276,7 @@
require ABSPATH . WPINC . '/html-api/class-wp-html-stack-event.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-processor-state.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-processor.php';
+require ABSPATH . WPINC . '/html-api/class-wp-html-template.php';
require ABSPATH . WPINC . '/class-wp-block-processor.php';
require ABSPATH . WPINC . '/class-wp-http.php';
require ABSPATH . WPINC . '/class-wp-http-streams.php';
diff --git a/templating-examples.php b/templating-examples.php
new file mode 100644
index 0000000000000..2c9e4e80b83f3
--- /dev/null
+++ b/templating-examples.php
@@ -0,0 +1,205 @@
+Error: This is not a valid feed template.' ), '', array( 'response' => 404 ) );
+
+// src/wp-includes/functions.php:1844-1845
+// Link with placeholder inside translation
+$wpdb->error = sprintf(
+ /* translators: %s: Database repair URL. */
+ __( 'One or more database tables are unavailable. The database may need to be repaired.' ),
+ 'maint/repair.php?referrer=is_blog_installed'
+);
+
+// src/wp-admin/edit-form-advanced.php:185
+// HTML wrapper injected as sprintf parameter
+$messages['post'][9] = sprintf( __( 'Post scheduled for: %s.' ), '' . $scheduled_date . '' ) . $scheduled_post_link_html;
+
+// src/wp-admin/revision.php:145-147
+// Multiple strong tags for emphasis in help text
+$revisions_overview .= '
' . __( 'To navigate between revisions, drag the slider handle left or right or use the Previous or Next buttons.' ) . '
';
+$revisions_overview .= '
' . __( 'Compare two different revisions by selecting the “Compare any two revisions” box to the side.' ) . '
';
+$revisions_overview .= '
' . __( 'To restore a revision, click Restore This Revision.' ) . '
';
+
+// src/wp-includes/theme.php:978-979
+// Context translation (_x) with HTML error prefix
+return new WP_Error(
+ 'theme_wp_php_incompatible',
+ sprintf(
+ /* translators: %s: Theme name. */
+ _x( 'Error: Current WordPress and PHP versions do not meet minimum requirements for %s.', 'theme' ),
+ $theme->display( 'Name' )
+ )
+);
+
+// src/wp-includes/widgets/class-wp-widget-text.php:542
+// Complex: _e() with embedded link and HTML entities
+_e( 'Did you know there is a “Custom HTML” widget now? You can find it by pressing the “Add a Widget” button and searching for “HTML”. Check it out to add some custom code to your site!' );
+
+// src/wp-includes/blocks/comments-title.php:29
+// HTML entities (curly quotes) in translation
+$post_title = sprintf( __( '“%s”' ), get_the_title() );
+
+// src/wp-includes/blocks/latest-posts.php:164-166
+// Complex nested HTML with multiple escaped placeholders
+$trimmed_excerpt .= sprintf(
+ /* translators: 1: A URL to a post, 2: Hidden accessibility text: Post title */
+ __( '… Read more: %2$s' ),
+ esc_url( $post_link ),
+ esc_html( $title )
+);
+
+// src/wp-admin/includes/class-plugin-upgrader.php:60
+// HTML span wrapping another placeholder (double sprintf)
+$this->strings['downloading_package'] = sprintf( __( 'Downloading update from %s…' ), '%s' );
+
+// src/wp-admin/includes/privacy-tools.php:404
+// Code tag wrapping technical reference
+sprintf( __( 'The %s post meta must be an array.' ), '_export_data_grouped' );
+
+// src/wp-admin/widgets.php:24
+// Full paragraph with embedded documentation link
+wp_die( __( 'The theme you are currently using is not widget-aware, meaning that it has no sidebars that you are able to change. For information on making your theme widget-aware, please follow these instructions.' ) );
+
+// src/wp-admin/revision.php:158
+// Link in translation without placeholders
+$revisions_sidebar .= '