Files
AsterionWP2026/tools/av-wpml-push-translations.php
j.foucher d2badf49d4 feat(bricks+wpml): full programmatic page generation + native WPML CTE integration
Sessions 3-4 deliverables. Establishes the Asterion content pipeline :
EN pages generated programmatically from content-source/*-data.php, FR
translations rebuilt natively by Bricks's WPML integration from EN structure
+ WPML strings (no direct FR _bricks_page_content_2 writes).

## tools/_av-bricks-shared.php (the library)
Shared helpers + element atoms (eyebrow/heading/text/button/text-link) +
section builders (hero/prose/list/cards/cta/upsell/closer) + page write
pipeline that handles : write EN content via $wpdb (bypass Bricks's filter),
force Bricks::Elements::load_elements() so controls are populated, fire
wpml_page_builder_register_strings, push dict FR into icl_string_translations
(skip user CTE edits, auto-prefix /fr on internal URLs), trigger Bricks
native FR rebuild via wpml_page_builder_string_translated, and ensure a CTE
job exists (icl_translate_job.editor='wpml' + revision=NULL + populate
icl_translate rows with base64+gzip-encoded EN/FR strings).

## tools/av-generate-{industries,solutions}.php
Thin wrappers : URL trigger (template_redirect, not init — Bricks elements
load on 'wp' hook), add_filter('av_fr_translation_dict', ...) to register
FR strings, content-type-specific hub builder. ~150 FR strings per type.

## tools/av-translate-{industries,solutions}-fr.php
One-shot seeds for the initial FR linked posts via WPML trid mapping.

## tools/av-wpml-* operational mu-plugins
- set-cte : switch doc_translation_method to ICL_TM_TMETHOD_EDITOR (=1)
- create-jobs : reset CTE jobs (status=10 + needs_update=1 + editor=wpml +
  revision=NULL). Integrated into pipeline via av_ensure_wpml_cte_job().
- fix-status / repair : align FR posts to correct trid + cleanup orphans.
- push-translations : push dict into icl_string_translations.
- plugins-check / debug / dump-strings / icl-dump / rescan : diagnostics.

## tools/av-fix-link-types.php
One-shot DB migration : convert legacy link.type='internal'+url to 'external'
across all _bricks_page_*_2 meta values (JSON and PHP-serialized). Bricks's
set_link_attributes() only renders href= for type='external' — older session-3
templates lost their href silently.

## Other tools/ mu-plugins
- av-bricks-rest-bridge : pivot — exposes Bricks CPTs over REST, dual-format
  read filter (JSON for MCP / array for renderer), auto-mirror content→header/
  footer for templates.
- av-apply-bricks-styles, av-cleanup-orphans, av-create-wp-menu, av-debug-meta,
  av-dump-bricks, av-fix-template-meta : misc operational scripts.

## Bricks Config/
JSON snapshots of bricks_theme_styles, bricks_color_palette,
bricks_global_settings for diffable inspection.

## .gitignore
Add .mcp.json — holds per-developer WP App Password for the Bricks MCP server.

## wp-content/themes/asterion-bricks/assets/css/utilities.css
Note that all responsive overrides are now emitted natively by the page
generator via Bricks per-breakpoint settings, no custom CSS needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:40:28 +02:00

123 lines
5.0 KiB
PHP

<?php
/**
* Plugin Name: Asterion — Push dict translations into WPML's icl_strings (so ATE shows them)
* Description:
* /?av_wpml_strings_dump=1 → dump registered Bricks strings + their FR translation state
* /?av_wpml_strings_push=1 → for each EN string in icl_strings (Bricks context), if our
* av_fr_translation_dictionary() has a match, write the FR
* translation via icl_add_string_translation() with status=10.
*
* Goal : ATE shows our programmatic translations pre-filled when the linguist opens a page.
* Editing in ATE then updates icl_string_translations → WPML rebuilds the FR Bricks JSON.
*/
defined('ABSPATH') || exit;
require_once __DIR__ . '/_av-bricks-shared.php';
add_action('init', function () {
if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) return;
if (! empty($_GET['av_wpml_strings_dump'])) av_wpml_strings_dump();
if (! empty($_GET['av_wpml_strings_push'])) av_wpml_strings_push();
});
function av_wpml_strings_dump() {
global $wpdb;
header('Content-Type: text/plain; charset=utf-8');
// 1) Count strings per context
$rows = $wpdb->get_results(
"SELECT context, COUNT(*) AS n FROM {$wpdb->prefix}icl_strings GROUP BY context ORDER BY n DESC"
);
echo "Strings registered (icl_strings) by context :\n";
foreach ($rows as $r) echo sprintf(" %-40s %d strings\n", $r->context, $r->n);
// 1b) Sample strings whose VALUE matches our content (find which context Bricks uses for page content)
echo "\nSearching for our content strings :\n";
foreach (['PROSERVE', 'Police training', 'Built for those', 'mini-case', 'Industries', 'Solutions'] as $needle) {
$found = $wpdb->get_results($wpdb->prepare(
"SELECT context, name, LEFT(value, 70) AS preview FROM {$wpdb->prefix}icl_strings WHERE value LIKE %s LIMIT 3",
'%' . $wpdb->esc_like($needle) . '%'
));
echo " needle '$needle' : " . count($found) . " match(es)\n";
foreach ($found as $f) echo " context='{$f->context}' name='{$f->name}' value='{$f->preview}'\n";
}
// 2) Sample a few Bricks strings + their FR (context is just 'bricks', not per-page)
echo "\nSample Bricks strings (10 of 156) :\n";
$strings = $wpdb->get_results(
"SELECT id, name, LEFT(value, 80) AS preview, status FROM {$wpdb->prefix}icl_strings WHERE context='bricks' LIMIT 10"
);
foreach ($strings as $s) {
echo " id={$s->id} name={$s->name} status={$s->status}\n EN: {$s->preview}\n";
$fr = $wpdb->get_var($wpdb->prepare(
"SELECT LEFT(value, 80) FROM {$wpdb->prefix}icl_string_translations WHERE string_id=%d AND language='fr' LIMIT 1",
$s->id
));
echo " FR: " . ($fr !== null ? $fr : '(no translation)') . "\n";
}
// 3) Count translations by language and status
$tr_rows = $wpdb->get_results(
"SELECT language, status, COUNT(*) AS n
FROM {$wpdb->prefix}icl_string_translations GROUP BY language, status"
);
echo "\nString translations summary :\n";
foreach ($tr_rows as $r) echo " lang={$r->language} status={$r->status} : {$r->n}\n";
exit;
}
function av_wpml_strings_push() {
global $wpdb;
header('Content-Type: text/plain; charset=utf-8');
if (! function_exists('icl_add_string_translation')) {
echo "FAIL : icl_add_string_translation() not available\n";
exit;
}
// Trigger the dict to load (with all filter contributions from generators)
$dict = av_fr_translation_dictionary();
echo "Loaded FR dictionary : " . count($dict) . " EN→FR mappings\n\n";
// All Bricks page-content strings (per-page contexts : bricks-{POST_ID})
$strings = $wpdb->get_results(
"SELECT id, value, context FROM {$wpdb->prefix}icl_strings WHERE context LIKE 'bricks-%'"
);
echo "Found " . count($strings) . " EN strings registered for Bricks pages\n\n";
$matched = 0;
$unmatched = 0;
$written = 0;
foreach ($strings as $s) {
if (! isset($dict[$s->value])) {
$unmatched++;
continue;
}
$matched++;
$fr = $dict[$s->value];
// ICL_TM_COMPLETE = 10
$result = icl_add_string_translation((int) $s->id, 'fr', $fr, 10);
if ($result) $written++;
}
echo "Matched in dict : $matched\n";
echo "Written to icl_string_translations : $written\n";
echo "Unmatched (no FR in dict, will stay EN in ATE) : $unmatched\n";
if ($unmatched > 0 && $unmatched < 100) {
echo "\nSample unmatched EN strings (extend the dict to translate these) :\n";
$shown = 0;
foreach ($strings as $s) {
if (! isset($dict[$s->value]) && $shown < 15) {
echo " - " . substr($s->value, 0, 120) . (strlen($s->value) > 120 ? '...' : '') . "\n";
$shown++;
}
}
}
echo "\nDONE. Refresh ATE — FR strings should be pre-filled.\n";
exit;
}