Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.11.0]
### Features
- ACF blocks in Gutenberg: field data stored in the block-comment JSON of `acf/*` blocks (`attrs.data`) is now translated automatically, including repeater/group sub-keys (`slides_0_caption`). Short values are batched into a single AI call; the client translation workflow exposes ACF block fields as their own units (`acf_block:{path}:{key}`).
- ACF options pages: new MCP ability `ai-translate/translate-options` translates ACF fields on options pages (global content such as footer texts or CTAs). WPGlobus/WP Multilang write inline language markup; Polylang-style per-language storage is opt-in via the `slytranslate_acf_options_post_id` filter; TranslatePress reports `not_required` because its string table already covers option output.
- Meta-key exclusion list: new `slytranslate_meta_keys_exclude` option (whitespace-separated, also exposed through the `configure` ability as `meta_keys_exclude`) excludes keys from translation uniformly across manual, SEO-detected, and field-plugin-resolved keys. Implemented as a priority-5 callback on `slytranslate_translate_meta_key`, so third-party filters can still override it.
- Per-field opt-out in the ACF field editor: a new "Exclude from AI translation" toggle (`slytranslate_exclude`) in every ACF field definition skips that field during automatic translation.
- Structured field types: ACF `link` fields are now translated selectively — only the `title` sub-key is sent to the model, `url`/`target` are copied unchanged. Extensible via the new `slytranslate_meta_value_translation_spec` filter.
- Meta Box (metabox.io) support: `text`, `textarea`, and `wysiwyg` fields are detected via the field registry and translated automatically (filter: `slytranslate_metabox_translatable_field_types`).
- Pods support: `text`, `paragraph`, and `wysiwyg` fields are detected via the pod definition and translated automatically (filter: `slytranslate_pods_translatable_field_types`).
- New introspection helper `MetaTranslationService::describe_effective_meta_keys()` reports every effective meta key with its source (`acf`/`seo`/`manual`/`filter`), ACF field label/type, and exclusion state — groundwork for the settings UI and a future `get-translatable-fields` ability.
- Taxonomy-term translation (opt-in via `translate_terms`): when a translated post references terms without a target-language translation, Polylang setups now create and link AI-translated terms instead of silently dropping categories/tags. New MCP ability `ai-translate/translate-terms` bulk-translates whole taxonomies for existing sites (with `dry_run` preview).
- Image alt texts: `_wp_attachment_image_alt` is now translated by default (new `default` source in the effective meta-key resolution; the exclusion list and filter API can veto it). Polylang media duplication (`pll_translate_media`) translates alt text, caption, and description of the duplicated attachment, and post translation back-fills the featured image's alt text on the target attachment when it is still empty.
- Background processing for bulk jobs: new `TranslationQueue` runs one post per scheduled action — Action Scheduler when available, WP-Cron single events otherwise. `translate-content-bulk` accepts `background=true` and returns a `job_id`; `get-progress` and `cancel-translation` accept `job_id` to poll and cancel queue jobs. Jobs survive closed tabs and PHP timeouts; the browser-driven REST path remains the fallback for sites without a working cron.
- Auto-translate on publish: the previously unused `slytranslate_new_post` option is now consumed — publishing a source-language post queues draft translations for all missing target languages (never auto-published; requires the background queue). Plugin-created translations carry a `_slytranslate_generated` marker as loop guard.
- Glossary / do-not-translate list: new `slytranslate_glossary` option (exposed via `configure` as `glossary`) with `keep` entries (never translate) and `translate` entries (fixed per-language translations). Injected into every prompt as a compact block; large glossaries are pre-filtered to terms present in the source text.
- Slug translation (opt-in via `translate_slugs`): slugs of newly created translations are derived from the translated title via `sanitize_title()` + `wp_unique_post_slug()` — no extra model call. Existing translation slugs are never touched unless they are still the auto-generated stub.
- Diff-based retranslation: overwriting an existing translation now hashes source blocks and meta values (`_slytranslate_unit_hashes` / `_slytranslate_meta_hashes` on the target) and only sends changed blocks/keys through the model; unchanged parts are reused from the existing translation. Falls back to full retranslation when the block structure diverged. The shared fingerprint helper `TranslationFingerprint` also backs the client workflow's stale-source detection.
- New readonly MCP ability `ai-translate/get-translatable-fields`: reports which meta fields a `translate-content` call would translate or clear, including per-key source attribution (`manual`/`seo`/`acf`/`default`/`filter`), exclusion state, detected SEO plugin, and active field-plugin resolvers.
- Admin settings page (Settings → SlyTranslate): the first in-WordPress configuration surface. Status row (detected language/SEO/field plugins, AI-client availability, effective model), model picker with refresh, site-wide prompt add-on, glossary editor, meta-field list with per-key exclusion checkboxes (fed by `get-translatable-fields`, including a per-post preview), automation toggles, and a collapsed Advanced section (prompt template with reset, context-window override, direct-API URL with probe status, string-table concurrency with a "Test concurrency" probe shown only for string-table adapters, transport diagnostics). Saves through the same `ConfigurationService::save()` path as the MCP `configure` ability — probe side effects and SSRF validation apply identically. New REST route `ai-translate/probe-string-table-concurrency/run` exposes the concurrency probe to the UI.
- The settings page and the ACF field-editor toggle ship with German translations (`de_DE`); the settings app receives its strings server-side via `wp_localize_script`, so no JS translation files are needed.

### Changes
- Shared ACF introspection extracted into `AcfFieldIntrospector`; `AcfMetaResolver` is now a thin consumer of it. The `slytranslate_acf_translatable_field_types` filter is unchanged.
- `WpglobusAdapter` and `WpMultilangAdapter` expose a public `merge_language_variant()` mutation helper used by the options translation service.

## [1.10.0]
### Features
- ACF (Advanced Custom Fields) support: translatable `text`, `textarea`, and `wysiwyg` fields are now detected and translated automatically — no configuration required when ACF is active.
Expand Down
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ It works with any LLM available through a WordPress AI connector and natively su
- Translates selected text or entire Gutenberg blocks inline, without leaving the editor
- Exposes the same functionality as MCP abilities, so external LLM tools (Claude Code, Codex, and others) can drive translations programmatically
- Carries SEO metadata (title, description) through the same translation workflow as the post content
- Translates Advanced Custom Fields (ACF) `text`, `textarea`, and `wysiwyg` fields automatically — no configuration required when ACF is active
- Translates custom fields from ACF (including ACF blocks and options pages), Meta Box, and Pods automatically — no configuration required when the field plugin is active
- Handles long and structured content with chunking and output validation
- Supports model-specific profiles that tune prompt style and retry behavior for known model families

Expand Down Expand Up @@ -130,9 +130,11 @@ In the MCP workflow the LLM Wrapper box at the top also acts as the translation
| `ai-translate/translate-text` | Translate arbitrary text |
| `ai-translate/translate-blocks` | Translate serialized Gutenberg blocks |
| `ai-translate/translate-content` | Create or update one translated post/page/CPT entry |
| `ai-translate/translate-content-bulk` | Bulk-translate multiple entries |
| `ai-translate/get-progress` | Return live progress for a running translation |
| `ai-translate/cancel-translation` | Cancel a running translation |
| `ai-translate/translate-content-bulk` | Bulk-translate multiple entries (`background=true` queues the batch via Action Scheduler/WP-Cron) |
| `ai-translate/translate-terms` | Bulk-translate taxonomy terms and link them as translations (Polylang only) |
| `ai-translate/get-translatable-fields` | Introspect which meta fields a translation would translate or clear, with source attribution |
| `ai-translate/get-progress` | Return live progress for a running translation or a queued background job (`job_id`) |
| `ai-translate/cancel-translation` | Cancel a running translation or a queued background job (`job_id`) |
| `ai-translate/get-available-models` | List models from configured connectors |
| `ai-translate/save-additional-prompt` | Save per-user additional instructions |
| `ai-translate/configure` | Read or update persistent plugin settings |
Expand Down Expand Up @@ -170,7 +172,9 @@ For reliable results in agent workflows:

**Field plugins** (custom fields translated alongside content)

- Advanced Custom Fields (ACF) — Free and Pro, including Repeater and Flexible Content
- Advanced Custom Fields (ACF) — Free and Pro, including Repeater and Flexible Content, ACF blocks in Gutenberg, and options pages
- Meta Box (metabox.io)
- Pods

**SEO plugins** (metadata translated alongside content)

Expand Down Expand Up @@ -238,7 +242,11 @@ add_filter( 'slytranslate_acf_translatable_field_types', function ( $types ) {
} );
```

To exclude a specific field or add one that is not ACF-registered, use the `slytranslate_translate_meta_key` filter:
To exclude fields there are three options, from simplest to most flexible:

1. **ACF field editor** — enable the "Exclude from AI translation" toggle in the field's settings.
2. **Exclusion list** — add the meta keys (whitespace-separated) to the `meta_keys_exclude` setting (via the `configure` MCP ability or the `slytranslate_meta_keys_exclude` option). Works for any meta key, not just ACF.
3. **Filter API** — use the `slytranslate_translate_meta_key` filter, which has the final say:

```php
add_filter( 'slytranslate_translate_meta_key', function ( $translate, $meta_key ) {
Expand All @@ -249,6 +257,21 @@ add_filter( 'slytranslate_translate_meta_key', function ( $translate, $meta_key
}, 10, 2 );
```

**Are ACF blocks and options pages covered?**
Yes. Field data stored inside `acf/*` Gutenberg blocks is translated automatically along with the post content, including repeater sub-fields. ACF `link` fields translate only the link title — URL and target are never sent to the model.

ACF fields on options pages (global content such as footer texts or CTAs) are translated through the `ai-translate/translate-options` MCP ability. With WPGlobus or WP Multilang the translated variant is merged into the option value; with TranslatePress nothing needs to happen (its string table already translates option output). Polylang has no language-aware option storage — opt in to per-language storage via the `slytranslate_acf_options_post_id` filter:

```php
// Polylang pattern: store translated options under options_{lang}
add_filter( 'slytranslate_acf_options_post_id', function ( $post_id, $target_language ) {
return 'options_' . $target_language;
}, 10, 2 );
```

**Do Meta Box or Pods fields get translated automatically?**
Yes. Translatable field types (`text`, `textarea`, `wysiwyg` for Meta Box; `text`, `paragraph`, `wysiwyg` for Pods) are detected through each plugin's field registry. The type lists are extensible via the `slytranslate_metabox_translatable_field_types` and `slytranslate_pods_translatable_field_types` filters.

**Does this work without a language plugin?**
Yes, for inline text and block translation. Content translation workflows (full post/page) require a supported language plugin.

Expand All @@ -268,7 +291,7 @@ In the list-table dialog, overwrite is off by default. If a translation already
Yes, when using Polylang. Call `ai-translate/set-post-language` with `post_id` and `target_language`. Use `force` to bypass conflict checks and `relink=true` to rewrite translation relations. Not available in WP Multilang mode.

**How do I control the prompt and translation style?**
Use `ai-translate/configure` for persistent defaults. Pass `additional_prompt` on any `translate-*` call for per-request instructions.
Use the settings page (Settings → SlyTranslate) or `ai-translate/configure` for persistent defaults — both write through the same backend. Pass `additional_prompt` on any `translate-*` call for per-request instructions.

**Why does `execute-ability` fail even when discovery looks correct?**
Some external WordPress MCP adapter wrappers expose a flatter `execute-ability` signature. If `discover-abilities` shows the correct SlyTranslate schema but `execute-ability` still errors about a missing `parameters` wrapper, investigate the external adapter layer — SlyTranslate controls ability names, descriptions, and schemas, not the wrapper surface.
Expand Down
Loading