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();