Files
AsterionWP2026/wp-content/themes/asterion-bricks/inc/i18n.php
j.foucher f18a57e83f feat(wpml): refactor to post_content + duplicate site in French via WPML
This commit makes the site editable from WP admin (Gutenberg or Bricks
"Edit with Bricks") and translatable via WPML Translation Editor by
shifting page rendering away from Custom Templates and into post_content.

Architecture change
-------------------
Before: Custom Templates assigned to each page (page-solution-detail.php
etc.) generated the page HTML at runtime by reading from inc/*-data.php
arrays. WPML had nothing to translate, Bricks Builder had nothing to
edit, the client could only modify content by editing PHP.

After:
- Pages now hold their full Gutenberg HTML inside post_content
- The default page.php template renders the_content() — no Custom
  Templates assigned anymore
- Bricks Builder picks up post_content and converts it to its element
  tree when "Edit with Bricks" is clicked
- WPML Translation Editor exposes every block for translation
- The client can edit pages directly in WP admin

What this commit ships
----------------------
- inc/render-blocks.php: data array → Gutenberg HTML payload generator
  with helpers per section type (prose / cards / list / cta) and per
  page archetype (solution / industry / technology / editorial / form
  / overview hub).
- inc/shortcodes.php: [av_form slug="request-demo"] for dynamic form
  blocks (forms can't live as static HTML — they need nonces and
  per-request state).
- inc/i18n.php: av_lang() / av_t() helpers for inline string translation
  in header.php and footer.php (covers ~80 UI strings); switches based
  on WPML's wpml_current_language filter.
- page.php: minimal default template — get_header() + the_content() +
  get_footer().
- header.php: real WPML language switcher driven by
  wpml_active_languages filter (replaces the placeholder EN · FR
  toggle); UI strings now route through av_t().
- footer.php: same UI string treatment.
- Custom Templates retained but no longer assigned (pages were stripped
  by the migration mu-plugin); they remain available if a page wants
  to opt back into the data-file pattern.

Operational migrations (one-shot mu-plugins, not versioned)
-----------------------------------------------------------
1. asterion-migrate-to-blocks.php walked all 26 PHP-templated pages,
   pre-rendered them via the new render-blocks helpers, wrote the
   result to post_content, and stripped _wp_page_template.
2. asterion-duplicate-fr.php cloned all 35 pages into French via WPML
   API, preserved parent/child hierarchy, linked translations through
   the trid table, marked them as duplicates pending refinement.
3. asterion-fix-fr-slugs.php aligned French slugs with English ones
   (WP's wp_unique_post_slug() had appended -2 suffixes; we bypass
   via $wpdb->update so /fr/solutions/ resolves correctly).

Verified
--------
- All 35 EN URLs return HTTP 200
- All 35 FR URLs return HTTP 200 under /fr/<slug>/
- Language switcher in header outputs correct hreflang links
- WPML emits hreflang en / fr / x-default in <head>
- Body class confirms wp-child-theme-asterion-bricks active
- Bricks frontend CSS still enqueued
- Inter Variable still preloaded

Next steps
----------
- French content is currently the duplicated EN copy. Translate via
  WP admin → WPML → Translation Editor (per-block UI), or edit pages
  directly in /fr/<slug>/ via Gutenberg / Bricks
- WPML String Translation can scan the theme to surface av_t() / __()
  strings for UI-level translation overrides

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:11:42 +02:00

201 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Asterion Bricks — i18n helpers
*
* Lightweight WPML-aware language helpers used by Custom Templates to pick
* the right data file (e.g. solutions-data.php vs solutions-data-fr.php) and
* by header.php / footer.php to translate UI strings.
*
* We don't use WordPress .po/.mo loaders — WPML String Translation handles
* those — but we keep a small in-PHP map for the strings that aren't tied
* to data files (nav labels, generic CTAs, footer columns).
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
/**
* Active language code, normalised to 'en' or 'fr'.
* Falls back to 'en' when WPML isn't active.
*/
function av_lang() {
$lang = apply_filters('wpml_current_language', null);
if (! $lang) {
$lang = function_exists('get_locale') ? substr(get_locale(), 0, 2) : 'en';
}
return ($lang === 'fr') ? 'fr' : 'en';
}
/**
* Build the path to a localised data file.
* Example : av_data_file('solutions-data') returns
* '<theme>/inc/solutions-data.php' when EN
* '<theme>/inc/solutions-data-fr.php' when FR (if it exists)
*/
function av_data_file($basename) {
$base = AV_THEME_DIR . '/inc/' . $basename;
$localised = $base . '-' . av_lang() . '.php';
return file_exists($localised) ? $localised : $base . '.php';
}
/**
* Translate a UI string. Falls back to the original on EN or when no entry
* matches in FR. Strings are kept in this file rather than .po/.mo so the
* theme stays self-contained.
*/
function av_t($string, $textdomain = 'asterion-bricks') {
if (av_lang() !== 'fr') {
return $string;
}
static $map = null;
if ($map === null) {
$map = [
// Nav + header
'Skip to main content' => 'Aller au contenu principal',
'Primary' => 'Principal',
'Mobile primary' => 'Principal mobile',
'Solutions' => 'Solutions',
'Industries' => 'Secteurs',
'Technology' => 'Technologie',
'Why Asterion' => 'Pourquoi Asterion',
'Insights' => 'Insights',
'Open menu' => 'Ouvrir le menu',
'Close menu' => 'Fermer le menu',
'Language' => 'Langue',
'Switch to French' => 'Passer en français',
'Switch to English' => 'Switch to English',
'Request a Demo' => 'Demander une démo',
// Footer
'Newsletter' => 'Newsletter',
'Our story' => 'Notre histoire',
'Customers' => 'Clients',
'Trust & Compliance' => 'Confiance et conformité',
'Partners' => 'Partenaires',
'Police' => 'Police',
'Special Forces' => 'Forces spéciales',
'Military' => 'Militaires',
'Firefighters' => 'Pompiers',
'Privacy' => 'Confidentialité',
'Terms' => 'Conditions',
'Cookies' => 'Cookies',
'Legal notice' => 'Mentions légales',
'All rights reserved.' => 'Tous droits réservés.',
'Insights from the front of immersive training. One email per month. No spam.'
=> 'Les enseignements du terrain de la formation immersive. Un e-mail par mois. Pas de spam.',
'Subscribe' => 'Sabonner',
'your@email.com' => 'vous@email.com',
'The modular, sovereign XR platform for European security forces. Train like the threat is now.'
=> 'La plateforme XR modulaire et souveraine pour les forces de sécurité européennes. Entraînez-vous comme si la menace était maintenant.',
// Generic CTAs / labels
'Read more' => 'Lire la suite',
'See all articles' => 'Voir tous les articles',
'Watch the 90-second film' => 'Voir le film de 90 secondes',
'Schedule a demo' => 'Planifier une démo',
'Request the kit' => 'Demander le kit',
'Get a quote' => 'Demander un devis',
'Send' => 'Envoyer',
'Send the message' => 'Envoyer le message',
'Apply for a T&E Kit' => 'Postuler pour un kit T&E',
'Schedule the demo' => 'Planifier la démo',
'Request the quote' => 'Demander le devis',
'Read the case study' => 'Lire létude de cas',
'Discuss a custom scenario' => 'Discuter dun scénario sur mesure',
'Schedule a discovery call' => 'Planifier un appel de cadrage',
'Discuss your custom request' => 'Parler de votre besoin sur mesure',
'Become the next case study' => 'Devenez la prochaine étude de cas',
'Request a Trust Pack' => 'Demander un Trust Pack',
'Become a partner — apply' => 'Devenir partenaire — candidater',
'Compare with PROSERVE FLEX' => 'Comparer avec PROSERVE FLEX',
'Compare with PROSERVE ACADEMY' => 'Comparer avec PROSERVE ACADEMY',
'Comparison and upgrade' => 'Comparaison et évolution',
'Eligibility' => 'Éligibilité',
'Frequently asked' => 'Questions fréquentes',
'Quick answers.' => 'Réponses brèves.',
// Section eyebrows used on home / hubs / forms
'Modular XR training for security forces' => 'Formation XR modulaire pour les forces de sécurité',
'Trusted by' => 'Reconnu par',
'Trusted by security forces across Europe' => 'Adopté par les forces de sécurité en Europe',
'Voices from the front of immersive training.' => 'La parole des opérateurs.',
'Compare' => 'Comparer',
'The six pillars' => 'Les six piliers',
'Tell us about your unit.' => 'Parlez-nous de votre unité.',
'What you receive' => 'Ce que vous recevez',
'Other inquiries' => 'Autres demandes',
'How can we help?' => 'Comment pouvons-nous vous aider ?',
// Form widgets
'— select —' => '— choisir —',
'Your email' => 'Votre e-mail',
'Newsletter signup' => 'Inscription newsletter',
'Mobile drawer' => 'Tiroir mobile',
'Voices from the field' => 'La parole du terrain',
'Train how it really feels.' => 'Entraînez-vous au plus près du réel.',
'Train like the threat is now.' => 'Entraînez-vous comme si la menace était maintenant.',
'Three paths into the platform — pick the one that fits where you are.' => 'Trois portes dentrée dans la plateforme — choisissez celle qui correspond à votre besoin.',
'A 30-minute tailored session with our team. Walk through the configuration that matches your needs.' => 'Une session sur mesure de 30 minutes avec notre équipe. Démonstration de la configuration adaptée à vos besoins.',
'A 30-minute tailored session with our team.' => 'Une session de 30 minutes avec notre équipe.',
'A PROSERVE case on loan at your facility for 5 to 10 days, with on-site support from our team.' => 'Un kit PROSERVE en prêt dans vos locaux pendant 5 à 10 jours, avec accompagnement sur site.',
'A PROSERVE case on loan at your facility for 5 to 10 days.' => 'Un kit PROSERVE en prêt dans vos locaux pendant 5 à 10 jours.',
'A detailed quote and deployment plan within 5 working days.' => 'Un devis détaillé et un plan de déploiement sous 5 jours ouvrés.',
'Ready to get hands-on?' => 'Prêt à passer à la pratique ?',
'Last updated:' => 'Dernière mise à jour :',
'Legal' => 'Légal',
'Placeholder.' => 'À compléter.',
'This page is to be drafted with legal counsel. The structure below outlines the sections expected.' =>
'Cette page sera rédigée avec votre conseil juridique. La structure ci-dessous liste les sections attendues.',
'Sections to include' => 'Sections à inclure',
'Police training' => 'Formation police',
'Special forces training' => 'Formation forces spéciales',
'Military training' => 'Formation militaire',
'Firefighter training' => 'Formation pompier',
'Insights from the front of immersive training.' => 'Les enseignements du terrain de la formation immersive.',
'See the full scenario library' => 'Voir la bibliothèque complète de scénarios',
'Inside the hardware' => 'Inside the hardware',
'Inside the software' => 'Inside the software',
'Inside weapons & tracking' => 'Inside weapons & tracking',
'Browse the scenario library' => 'Parcourir la bibliothèque',
'Inside the instructor cockpit' => 'Inside the instructor cockpit',
'Inside after-action review' => 'Inside after-action review',
'See how UWeD works' => 'Voir comment fonctionne UWeD',
'Explore After-Action Review' => 'Découvrir lAfter-Action Review',
'Explore PROSERVE+ customization' => 'Découvrir la personnalisation PROSERVE+',
'Discover MY PROSERVE' => 'Découvrir MY PROSERVE',
'Discover PROSERVE FLEX' => 'Découvrir PROSERVE FLEX',
'Discover PROSERVE ACADEMY' => 'Découvrir PROSERVE ACADEMY',
'Request access' => 'Demander laccès',
'Request a T&E Kit' => 'Demander un kit T&E',
'Request a Quote' => 'Demander un devis',
'Request a Demo for police use' => 'Demander une démo pour la police',
'Request a Demo for fire & emergency' => 'Demander une démo pour les pompiers',
'Thank you — your request was sent.' => 'Merci — votre demande a bien été envoyée.',
'We will reply within the timeframe stated above. Check your inbox (and spam) for confirmation.' =>
'Nous reviendrons vers vous dans les délais indiqués. Vérifiez votre boîte de réception (et vos spams) pour la confirmation.',
'Something went wrong.' => 'Une erreur est survenue.',
' Please try again, or email us directly at hello@asterionvr.com.' =>
' Veuillez réessayer, ou écrivez-nous directement à hello@asterionvr.com.',
'Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4.' =>
'Le blog éditorial démarrera avec la mise en ligne du site. Le calendrier sur 12 mois figure dans le livrable Stratégie & Contenu, partie B.4.',
'Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4. New articles will appear here as soon as they ship.' =>
'Le blog éditorial démarrera avec la mise en ligne du site. Le calendrier sur 12 mois figure dans le livrable Stratégie & Contenu, partie B.4. Les articles apparaîtront ici dès leur publication.',
'Subscribe — one article per month.' => 'Abonnez-vous — un article par mois.',
'Notes from the front of immersive training.' => 'Les enseignements du terrain de la formation immersive.',
'Doctrine. Training science. Instructor stories. AAR data. Comparisons of VR vs live training. Found something useful? It probably came from a customer conversation. We publish what we learn.' =>
'Doctrine. Science de la formation. Récits dinstructeurs. Données AAR. Comparaisons VR vs entraînement réel. Quelque chose dutile ? Cela vient probablement dun échange client. Nous publions ce que nous apprenons.',
'Everything to evaluate, justify, and procure.' => 'Tout pour évaluer, justifier, acheter.',
'Whether you are building an internal pitch, drafting a procurement specification, or briefing your team — these resources are designed to help.' =>
'Que vous prépariez un argumentaire interne, rédigiez un cahier des charges ou briefiez vos équipes — ces ressources sont là pour vous aider.',
];
}
return $map[$string] ?? $string;
}
/**
* Convenience: echo a translated string.
*/
function av_te($string) {
echo esc_html(av_t($string));
}