define_constants();
$this->load_dependencies();
$this->init_hooks();
}
/**
* System path definition maps.
*/
private function define_constants()
{
if (! defined('AF_TRANSLATE_FILE')) {
define('AF_TRANSLATE_FILE', __FILE__);
}
if (! defined('AF_TRANSLATE_BASE')) {
define('AF_TRANSLATE_BASE', plugin_basename(AF_TRANSLATE_FILE));
}
if (! defined('AF_TRANSLATE_DIR')) {
define('AF_TRANSLATE_DIR', dirname(AF_TRANSLATE_FILE));
}
if (! defined('AF_TRANSLATE_PATH')) {
define('AF_TRANSLATE_PATH', plugin_dir_path(__FILE__));
}
if (! defined('AF_TRANSLATE_URL')) {
define('AF_TRANSLATE_URL', plugin_dir_url(__FILE__));
}
// Add an optional indicator lookup helper
if (! defined('AF_TRANSLATE_IS_PRO')) {
define('AF_TRANSLATE_IS_PRO', class_exists('AF_Translate_Pro') || defined('AF_TRANSLATE_PRO_FILE'));
}
// =========================================================================
// DYNAMIC ASSET VERSION ENGINE (CACHE BUSTER)
// =========================================================================
if (! defined('AF_TRANSLATE_VERSION')) {
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
// Generates a fresh timestamp on every page load to completely bypass heavy caching during development
define('AF_TRANSLATE_VERSION', time());
} else {
// Production Fallback: Change this version number whenever you release a new update
define('AF_TRANSLATE_VERSION', '5.0.1');
}
}
}
/**
* High-performance memory-optimized asset loading schema.
*/
private function load_dependencies()
{
// Shared global multi-dimensional localization data configurations
require_once AF_TRANSLATE_PATH . 'includes/class-i18n-data.php';
require_once AF_TRANSLATE_PATH . 'includes/class-af-translate.php';
// Critical Performance Safeguard: Execute heavy analysis suites only on back-end requests
if (is_admin()) {
require_once AF_TRANSLATE_PATH . 'includes/class-bundle-analyzer.php';
require_once AF_TRANSLATE_PATH . 'includes/class-translate-scanner.php';
require_once AF_TRANSLATE_PATH . 'includes/class-translate-path-resolver.php';
require_once AF_TRANSLATE_PATH . 'includes/class-project-config.php';
require_once AF_TRANSLATE_PATH . 'includes/class-project-ajax.php';
require_once AF_TRANSLATE_PATH . 'includes/class-editor-view.php';
require_once AF_TRANSLATE_PATH . 'includes/class-file-pot-generator.php';
require_once AF_TRANSLATE_PATH . 'includes/class-file-compiler.php';
require_once AF_TRANSLATE_PATH . 'includes/class-file-stream.php';
require_once AF_TRANSLATE_PATH . 'includes/class-file-ajax.php';
require_once AF_TRANSLATE_PATH . 'includes/class-editor-path-validator.php';
require_once AF_TRANSLATE_PATH . 'includes/class-editor-po-processor.php';
require_once AF_TRANSLATE_PATH . 'includes/class-editor-ajax-handler.php';
require_once AF_TRANSLATE_PATH . 'includes/class-translate-ai-engine.php';
require_once AF_TRANSLATE_PATH . 'includes/class-translate-ai-ajax.php';
}
}
/**
* Standard runtime execution initialization hooks.
*/
private function init_hooks()
{
add_action('plugins_loaded', array($this, 'boot_core_engine'));
/**
* CRITICAL GLOBAL HOOKS: Intercepts and redirects PHP (.l10n.php / .mo) and JS (.json) translations.
* Must run globally (outside of is_admin) so frontend visitors and admin views see custom strings.
*/
// 1. WP 6.5+ Filter: Intercepts BOTH .l10n.php and .mo files
add_filter('load_translation_file', array($this, 'af_translate_load_from_uploads'), 10, 2);
// 2. WP < 6.5 Fallback Filter: Intercepts legacy .mo files
add_filter('load_textdomain_mofile', array($this, 'af_translate_load_from_uploads'), 10, 2);
// 3. Script Translation Filter: Intercepts Gutenberg / React JS Jed .json files
add_filter('load_script_translation_file', array($this, 'af_translate_load_json_from_uploads'), 10, 3);
add_filter('plugin_row_meta', [$this, 'plugin_row_meta'], 10, 2);
add_filter('plugin_action_links_' . AF_TRANSLATE_BASE, [$this, 'plugin_action_links']);
}
public function plugin_action_links($links)
{
$settings_link = sprintf('%2$s', admin_url('admin.php?page=af-translate'), esc_html__('Get Started', 'af-translate'));
array_unshift($links, $settings_link);
$links['af-translate-ai-connecter'] = sprintf('%2$s', admin_url('admin.php?page=af-translate-ai-connecter'), esc_html__('AI Settings', 'af-translate'));
return $links;
}
public function plugin_row_meta($plugin_meta, $plugin_file)
{
if (AF_TRANSLATE_BASE === $plugin_file) {
$row_meta = [
'af-translate-plugins' => sprintf('%2$s', admin_url('admin.php?page=af-translate-plugins'), esc_html__('Translate Plugins', 'af-translate')),
'af-translate-themes' => sprintf('%2$s', admin_url('admin.php?page=af-translate-themes'), esc_html__('Translate Themes', 'af-translate')),
];
$plugin_meta = array_merge($plugin_meta, $row_meta);
}
return $plugin_meta;
}
/**
* Intercepts PHP translation file loading for both .mo and WP 6.5+ .l10n.php formats.
*
* @param string $file Path to the translation file WP is attempting to load.
* @param string $domain Text domain.
* @return string Path to custom upload file if present, otherwise original path.
*/
public function af_translate_load_from_uploads($file, $domain)
{
if ('default' === $domain || empty($file)) {
return $file;
}
$filename = basename($file);
// 1. Determine requested file extension (.l10n.php or .mo)
$ext = '';
if (false !== strpos($filename, '.l10n.php')) {
$ext = '.l10n.php';
} elseif (false !== strpos($filename, '.mo')) {
$ext = '.mo';
} else {
return $file; // Exit if file type isn't supported
}
// 2. Extract locale pattern: {domain}-{locale}.{ext} or {locale}.{ext}
$pattern = '/^(?:' . preg_quote($domain, '/') . '-)?(.+)' . preg_quote($ext, '/') . '$/';
if (!preg_match($pattern, $filename, $matches)) {
return $file;
}
$locale = $matches[1];
$upload_dir = wp_upload_dir();
$base_path = trailingslashit($upload_dir['basedir']) . 'af-translate/';
$target_filename = $domain . '-' . $locale . $ext;
$directories = [
$base_path . 'themes/',
$base_path . 'plugins/',
$base_path . 'core/'
];
// 3. Search custom uploads sandbox for custom translation override
foreach ($directories as $dir) {
$found = $this->find_file_case_insensitive($dir, $target_filename);
if ($found) {
return $found;
}
}
return $file;
}
/**
* Redirects JS Script translation loading (.json) to uploads/af-translate/
*
* @param string|false $file Path to the JSON translation file WP found, or false.
* @param string $handle Script handle registered in WP.
* @param string $domain Text domain.
* @return string|false Path to custom JSON file if found, otherwise original.
*/
public function af_translate_load_json_from_uploads($file, $handle, $domain)
{
if ('default' === $domain) {
return $file;
}
$upload_dir = wp_upload_dir();
$base_path = trailingslashit($upload_dir['basedir']) . 'af-translate/';
$directories = [
$base_path . 'themes/',
$base_path . 'plugins/',
$base_path . 'core/'
];
$locale = determine_locale();
// 1. Check Exact WP File Match (Covers WP calculated MD5 hashes e.g. covernews-de_DE-33503a82....json)
if (!empty($file)) {
$filename = basename($file);
foreach ($directories as $dir) {
$found = $this->find_file_case_insensitive($dir, $filename);
if ($found) {
return $found;
}
}
}
// 2. Check Handle-Based Specific File (e.g., covernews-de_DE-aftheme-dashboard.json)
$handle_json = "{$domain}-{$locale}-{$handle}.json";
foreach ($directories as $dir) {
$found = $this->find_file_case_insensitive($dir, $handle_json);
if ($found) {
return $found;
}
}
// 3. Check Primary Domain File Fallback (e.g., covernews-de_DE.json)
$primary_json = "{$domain}-{$locale}.json";
foreach ($directories as $dir) {
$found = $this->find_file_case_insensitive($dir, $primary_json);
if ($found) {
return $found;
}
}
return $file;
}
/**
* Case-insensitive file search helper
*/
private function find_file_case_insensitive($directory, $filename)
{
$directory = wp_normalize_path($directory);
if (!is_dir($directory)) {
return false;
}
$target = trailingslashit($directory) . $filename;
if (file_exists($target)) {
return $target;
}
$lower_filename = strtolower($filename);
$files = scandir($directory);
if (is_array($files)) {
foreach ($files as $file) {
if (strtolower($file) === $lower_filename) {
return trailingslashit($directory) . $file;
}
}
}
return false;
}
/**
* Instantiates the foundational translation controller framework safely.
*/
public function boot_core_engine()
{
// 1. Boot the core foundational translation framework
if (class_exists('AF_Translate_Core')) {
new AF_Translate_Core();
}
// 2. Ignite the streaming download hooks for admin-post.php
if (class_exists('AF_Translate_File_Streaming')) {
AF_Translate_File_Streaming::init_hooks();
}
// 3. Editor Workbench AJAX Actions
if (class_exists('AF_Translate_Editor_AJAX_Handler')) {
add_action('wp_ajax_af_translate_save_pot', array('AF_Translate_Editor_AJAX_Handler', 'handle_save'));
}
// 4. Extended Utilities AJAX Actions
if (class_exists('AF_Translate_Ajax_Extended')) {
// Workspace Management
add_action('wp_ajax_af_translate_init_locale', array('AF_Translate_Ajax_Extended', 'ajax_init_po_handler'));
add_action('wp_ajax_af_translate_import_po', array('AF_Translate_Ajax_Extended', 'ajax_import_po_handler'));
// Direct Raw PO File Operations
add_action('wp_ajax_af_translate_save_po', array('AF_Translate_Ajax_Extended', 'ajax_save_po_handler'));
}
/**
* POLYMORPHIC ROUTER: Intercepts the shared action hook collision
* and safely directs traffic to the correct engine component.
*/
add_action('wp_ajax_af_translate_sync_po', array($this, 'route_sync_po_requests'));
/**
* Freemius.
*/
if (!class_exists('AF_Translate_Pro')) {
require_once(AF_TRANSLATE_DIR . '/freemius.php');
}
}
/**
* Polymorphic AJAX Router to handle shared action hook collisions safely.
* Evaluates parameters to differentiate between the editor screen and portability screen.
*/
public function route_sync_po_requests()
{
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Operational routing controller check; verification is safely deferred to downstream workbench sync execution context handlers.
if (isset($_POST['po_file_target']) && class_exists('AF_Translate_Editor_AJAX_Handler')) {
AF_Translate_Editor_AJAX_Handler::handle_sync();
exit;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Operational routing controller check; verification is safely deferred to downstream module dashboard sync handlers.
if (isset($_POST['component']) && class_exists('AF_Translate_Ajax_Extended')) {
AF_Translate_Ajax_Extended::ajax_sync_po_handler();
exit;
}
wp_send_json_error(esc_html__('Invalid transaction routing parameter signature.', 'af-translate'));
}
}
/**
* Accessor function shortcut to instantiate the architectural controller context.
*
* @return AF_Translate
*/
function af_translate_engine()
{
return AF_Translate::instance();
}
// Ignite operational plugin setup
af_translate_engine();