PluginProbe ʕ •ᴥ•ʔ
GPTranslate – Multilingual AI Translation for WordPress: Translate Websites with AI Agents / 2.17
GPTranslate – Multilingual AI Translation for WordPress: Translate Websites with AI Agents v2.17
2.34.2 2.34.1 2.34 2.33.10 2.33.9 2.33.8 2.33.7 2.33.6 2.33.5 2.33.2 2.32.10 2.33 2.33.1 2.32.6 2.32.7 2.32.8 trunk 2.10.3 2.10.4 2.10.5 2.10.6 2.11 2.12 2.13 2.14 2.14.1 2.15 2.15.1 2.16.1 2.16.2 2.17 2.18 2.18.1 2.18.2 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.25.1 2.25.2 2.26 2.27 2.27.10 2.27.5 2.28 2.28.1 2.29 2.30 2.31 2.32 2.32.5
gptranslate / multilang-routing.php
gptranslate Last commit date
assets 8 months ago flags 9 months ago language 9 months ago gptranslate.php 9 months ago multilang-routing.php 9 months ago readme.txt 8 months ago serverside-translations.php 9 months ago settings.php 9 months ago simplehtmldom.php 9 months ago uninstall.php 9 months ago
multilang-routing.php
603 lines
1 <?php
2 /**
3 * GPTranslate - Multilang Routing System for WordPress
4 * Handles language prefixes in URLs like /it/, /fr/, /en/, etc.
5 * and maps them to internal pages, similar to Joomla LanguageFilter plugin.
6 */
7
8 if (!defined('ABSPATH')) exit;
9
10 // Load only in frontend
11 if (! is_admin ()) {
12 $settings = get_option("gptranslate_options");
13
14 if ( ! function_exists( 'is_plugin_active' ) ) {
15 include_once ABSPATH . 'wp-admin/includes/plugin.php';
16 }
17 $polylangActive = is_plugin_active( 'polylang/polylang.php' );
18
19 $enabledLanguages = $settings['languages'] ?? [];
20
21 // Define supported languages (lowercase)
22 $gpt_supported_languages = array_map ( 'strtolower', [
23 'AF',
24 'SQ',
25 'AM',
26 'AR',
27 'HY',
28 'AZ',
29 'EU',
30 'BE',
31 'BN',
32 'BS',
33 'BG',
34 'CA',
35 'CEB',
36 'NY',
37 'ZH',
38 'CO',
39 'HR',
40 'CS',
41 'DA',
42 'NL',
43 'EN',
44 'EO',
45 'ET',
46 'TL',
47 'FI',
48 'FR',
49 'FY',
50 'GL',
51 'KA',
52 'DE',
53 'EL',
54 'GU',
55 'HT',
56 'HA',
57 'HAW',
58 'IW',
59 'HI',
60 'HMN',
61 'HU',
62 'IS',
63 'IG',
64 'ID',
65 'GA',
66 'IT',
67 'JA',
68 'JW',
69 'KN',
70 'KK',
71 'KM',
72 'KO',
73 'KU',
74 'KY',
75 'LO',
76 'LA',
77 'LV',
78 'LT',
79 'LB',
80 'MK',
81 'MG',
82 'MS',
83 'ML',
84 'MT',
85 'MI',
86 'MR',
87 'MN',
88 'MY',
89 'NE',
90 'NO',
91 'PS',
92 'FA',
93 'PL',
94 'PT',
95 'PA',
96 'RO',
97 'RU',
98 'SM',
99 'GD',
100 'SR',
101 'ST',
102 'SN',
103 'SD',
104 'SI',
105 'SK',
106 'SL',
107 'SO',
108 'ES',
109 'SU',
110 'SW',
111 'SV',
112 'TG',
113 'TA',
114 'TE',
115 'TH',
116 'TR',
117 'UK',
118 'UR',
119 'UZ',
120 'VI',
121 'CY',
122 'XH',
123 'YI',
124 'YO',
125 'ZU',
126 'ZT'
127 ] );
128
129 /**
130 * GPTranslate - Alias Translation Routing for WordPress
131 * This section augments the language prefix routing by checking translated aliases
132 * and routing them to the original page link.
133 */
134
135 add_action('init', function () use ($gpt_supported_languages, $polylangActive, $settings) {
136 if (is_admin()) return;
137
138 global $wpdb;
139
140 $subfolder = $settings['subfolder_installation'] ?? 0;
141 $rewriteAlias = $settings['rewrite_language_alias'] ?? 0;
142 $rewriteUrl = $settings['rewrite_language_url'] ?? 0;
143 $originalLang = $settings['language'] ?? 'en';
144
145 if (! $rewriteAlias || ! $rewriteUrl) return;
146
147 $requestUri = urldecode($_SERVER['REQUEST_URI']) ?? '';
148 $requestUri = sanitize_text_field(wp_unslash($requestUri));
149
150 // Remove subfolder if present
151 if ($subfolder) {
152 $homePath = wp_parse_url(home_url(), PHP_URL_PATH) ?? '';
153 if ($homePath && strpos($requestUri, $homePath) === 0) {
154 $requestUri = substr($requestUri, strlen($homePath));
155 }
156 }
157
158 // Normalize path
159 $rawUri = rtrim(home_url() . $requestUri, '/');
160 $rawUriWithSlash = $rawUri . '/';
161
162 $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
163 $pattern = '#^/(' . implode ( '|', array_map ( 'preg_quote', $gpt_supported_languages ) ) . ')(/.*)?$#i';
164
165 // Optional: remove subfolder if set
166 $subfolder_prefix = ''; // To hold subfolder if any
167 if ($settings['subfolder_installation']) {
168 $uri_parts = explode('/', ltrim($uri, '/'));
169
170 // Remove the first part (subfolder name)
171 $subfolder_prefix = '/' . array_shift($uri_parts);
172
173 // Rebuild URI without subfolder
174 $uri_to_match = '/' . implode('/', $uri_parts);
175 } else {
176 $uri_to_match = $uri;
177 }
178
179 if (preg_match ( $pattern, $uri_to_match, $matches )) {
180 $currentLang = strtolower ( $matches [1] );
181 } else {
182 return;
183 }
184
185 // Check translated alias
186 $table = $wpdb->prefix . 'gptranslate';
187 $query = $wpdb->prepare(
188 "SELECT pagelink FROM $table WHERE published = 1 AND languagetranslated = %s AND (
189 translated_alias = %s OR translated_alias = %s)",
190 $currentLang, $rawUri, $rawUriWithSlash );
191
192 $originalPageLink = $wpdb->get_var($query);
193
194 if (! $originalPageLink) return;
195
196 // Extract path from URL
197 $parsed = wp_parse_url($originalPageLink);
198 $originalPath = $parsed['path'] ?? '';
199
200 // Remove subfolder prefix again from final path
201 if ($subfolder && !empty($homePath) && strpos($originalPath, $homePath) === 0) {
202 $originalPath = substr($originalPath, strlen($homePath));
203 define('GPTRANSLATE_SUBFOLDER_REPLACED', true);
204 }
205
206 // Clean path
207 $originalPath = preg_replace('#^/?index\.php/?#i', '', $originalPath);
208 $originalPath = '/' . ltrim($originalPath, '/');
209
210 // Rewrite the request
211 $_SERVER['REQUEST_URI'] = $originalPath;
212 }, 1);
213
214 /**
215 * Detect language from URI and rewrite $_SERVER['REQUEST_URI'] accordingly defining a GPTRANSLATE_CURRENT_LANG constant
216 */
217 add_action ( 'init', function () use ($gpt_supported_languages, $polylangActive, $settings) {
218 if (is_admin()) return;
219
220 $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
221 $pattern = '#^/(' . implode ( '|', array_map ( 'preg_quote', $gpt_supported_languages ) ) . ')(/.*)?$#i';
222
223 // Optional: remove subfolder if set
224 $subfolder_prefix = ''; // To hold subfolder if any, avoid double removal
225 if ($settings['subfolder_installation'] && !defined('GPTRANSLATE_SUBFOLDER_REPLACED')) {
226 $uri_parts = explode('/', ltrim($uri, '/'));
227
228 // Remove the first part (subfolder name)
229 $subfolder_prefix = '/' . array_shift($uri_parts);
230
231 // Rebuild URI without subfolder
232 $uri_to_match = '/' . implode('/', $uri_parts);
233 } else {
234 $uri_to_match = $uri;
235 }
236
237 if (preg_match ( $pattern, $uri_to_match, $matches )) {
238 $lang = strtolower ( $matches [1] );
239 $new_uri = isset ( $matches [2] ) ? $matches [2] : '/';
240 if ($settings ['rewrite_language_url'] == 1 && !$polylangActive) {
241 $_SERVER ['REQUEST_URI'] = $subfolder_prefix . $new_uri;
242 }
243
244 if (! defined ( 'GPTRANSLATE_CURRENT_LANG' )) {
245 define ( 'GPTRANSLATE_CURRENT_LANG', $lang );
246 }
247 }
248 }, 2);
249
250 /**
251 * Replace and manage the lang attribute of the html tag
252 */
253 add_filter ( 'language_attributes', function ($output) use ($settings, $polylangActive) {
254 if (! defined ( 'GPTRANSLATE_CURRENT_LANG' ) || (!$settings['set_html_lang'] && !$settings ['rewrite_language_url']) || $polylangActive)
255 return $output;
256
257 $altflags_array = get_option ( 'gptranslate_options' ) ['alt_flags'] ?? [ ];
258
259 $lang_map = [
260 // Variazioni per EN
261 'en' => (in_array ( 'usa', $altflags_array ) || in_array ( 'canada', $altflags_array )) ? 'en-US' : 'en-GB',
262
263 // Variazioni per FR
264 'fr' => in_array ( 'quebec', $altflags_array ) ? 'fr-CA' : 'fr-FR',
265
266 // Variazioni per PT
267 'pt' => in_array ( 'brazil', $altflags_array ) ? 'pt-BR' : 'pt-PT',
268
269 // Variazioni per ES
270 'es' => in_array ( 'mexico', $altflags_array ) ? 'es-MX' : (in_array ( 'argentina', $altflags_array ) ? 'es-AR' : (in_array ( 'colombia', $altflags_array ) ? 'es-CO' : 'es-ES')),
271
272 // Tutte le altre senza variazioni
273 'af' => 'af-ZA',
274 'sq' => 'sq-AL',
275 'am' => 'am-ET',
276 'ar' => 'ar-SA',
277 'hy' => 'hy-AM',
278 'az' => 'az-AZ',
279 'eu' => 'eu-ES',
280 'be' => 'be-BY',
281 'bn' => 'bn-BD',
282 'bs' => 'bs-BA',
283 'bg' => 'bg-BG',
284 'ca' => 'ca-ES',
285 'ceb' => 'ceb-PH',
286 'ny' => 'ny-MW',
287 'zh' => 'zh-CN',
288 'zt' => 'zh-TW',
289 'co' => 'co-FR',
290 'hr' => 'hr-HR',
291 'cs' => 'cs-CZ',
292 'da' => 'da-DK',
293 'nl' => 'nl-NL',
294 'eo' => 'eo-EO',
295 'et' => 'et-EE',
296 'tl' => 'tl-PH',
297 'fi' => 'fi-FI',
298 'fy' => 'fy-NL',
299 'gl' => 'gl-ES',
300 'ka' => 'ka-GE',
301 'de' => 'de-DE',
302 'el' => 'el-GR',
303 'gu' => 'gu-IN',
304 'ht' => 'ht-HT',
305 'ha' => 'ha-NG',
306 'haw' => 'haw-US',
307 'iw' => 'iw-IL',
308 'hi' => 'hi-IN',
309 'hmn' => 'hmn-US',
310 'hu' => 'hu-HU',
311 'is' => 'is-IS',
312 'ig' => 'ig-NG',
313 'id' => 'id-ID',
314 'ga' => 'ga-IE',
315 'it' => 'it-IT',
316 'ja' => 'ja-JP',
317 'jw' => 'jw-ID',
318 'kn' => 'kn-IN',
319 'kk' => 'kk-KZ',
320 'km' => 'km-KH',
321 'ko' => 'ko-KR',
322 'ku' => 'ku-TR',
323 'ky' => 'ky-KG',
324 'lo' => 'lo-LA',
325 'la' => 'la-VA',
326 'lv' => 'lv-LV',
327 'lt' => 'lt-LT',
328 'lb' => 'lb-LU',
329 'mk' => 'mk-MK',
330 'mg' => 'mg-MG',
331 'ms' => 'ms-MY',
332 'ml' => 'ml-IN',
333 'mt' => 'mt-MT',
334 'mi' => 'mi-NZ',
335 'mr' => 'mr-IN',
336 'mn' => 'mn-MN',
337 'my' => 'my-MM',
338 'ne' => 'ne-NP',
339 'no' => 'no-NO',
340 'ps' => 'ps-AF',
341 'fa' => 'fa-IR',
342 'pl' => 'pl-PL',
343 'pa' => 'pa-IN',
344 'ro' => 'ro-RO',
345 'ru' => 'ru-RU',
346 'sm' => 'sm-WS',
347 'gd' => 'gd-GB',
348 'sr' => 'sr-RS',
349 'st' => 'st-LS',
350 'sn' => 'sn-ZW',
351 'sd' => 'sd-PK',
352 'si' => 'si-LK',
353 'sk' => 'sk-SK',
354 'sl' => 'sl-SI',
355 'so' => 'so-SO',
356 'su' => 'su-ID',
357 'sw' => 'sw-TZ',
358 'sv' => 'sv-SE',
359 'tg' => 'tg-TJ',
360 'ta' => 'ta-IN',
361 'te' => 'te-IN',
362 'th' => 'th-TH',
363 'tr' => 'tr-TR',
364 'uk' => 'uk-UA',
365 'ur' => 'ur-PK',
366 'uz' => 'uz-UZ',
367 'vi' => 'vi-VN',
368 'cy' => 'cy-GB',
369 'xh' => 'xh-ZA',
370 'yi' => 'yi-US',
371 'yo' => 'yo-NG',
372 'zu' => 'zu-ZA'
373 ];
374
375 $gpt_lang = strtolower ( GPTRANSLATE_CURRENT_LANG );
376 $full_lang = $lang_map [$gpt_lang] ?? $gpt_lang;
377
378 $output = 'lang="' . esc_attr ( $full_lang ) . '"';
379
380 // --- RTL support: set WP_Locale direction dynamically ---
381 global $wp_locale;
382 $rtl_languages = array("ar", "fa", "iw", "ps", "sd", "ur", "yi"); // Arabic, Hebrew, Persian, Urdu
383 if ( (int)$settings['auto_set_language_direction'] && in_array( $gpt_lang, $rtl_languages, true ) ) {
384 $wp_locale->text_direction = 'rtl';
385 // ensure HTML dir="rtl"
386 if ( strpos( $output, 'dir=' ) === false ) {
387 $output .= ' dir="rtl"';
388 } else {
389 $output = preg_replace( '/dir="ltr"/', 'dir="rtl"', $output );
390 }
391 return $output;
392 }
393
394 return $output;
395 } );
396
397 /**
398 * Inject alternate hreflang links in <head>
399 */
400 add_action ( 'wp_head', function () use ($enabledLanguages, $polylangActive, $settings) {
401 if (! defined ( 'GPTRANSLATE_CURRENT_LANG' ) || ! $settings ['add_alternate'] || ! $settings ['serverside_translations'] || $polylangActive) {
402 return;
403 }
404
405 $current_lang = GPTRANSLATE_CURRENT_LANG;
406 $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
407 $request_path = sanitize_text_field ( wp_unslash ( $request_uri ) );
408
409 // Remove query string
410 $request_path = strtok ( $request_path, '?' );
411
412 // Optional subfolder cleanup
413 if (!empty($settings['subfolder_installation'])) {
414 $home_path = wp_parse_url(home_url(), PHP_URL_PATH) ?? '';
415 if ($home_path && strpos($request_path, $home_path) === 0) {
416 $request_path = substr($request_path, strlen($home_path));
417 }
418 }
419
420 // Remove current language code from URL if present
421 $current_path = preg_replace ( '#^/' . preg_quote ( $current_lang, '#' ) . '(/|$)#', '/', $request_path );
422
423 $site_url = home_url ();
424
425 foreach ( $enabledLanguages as $lang ) {
426 $translated_url = rtrim ( $site_url, '/' ) . '/' . $lang . rtrim ( $current_path, '/' );
427
428 echo '<link rel="alternate" hreflang="' . esc_attr ( $lang ) . '" href="' . esc_url ( $translated_url . '/' ) . "\" />\n";
429
430 if ($lang === $settings ['language']) {
431 echo '<link rel="alternate" hreflang="x-default" href="' . esc_url ( $translated_url . '/' ) . "\" />\n";
432 }
433 }
434 } );
435
436 /**
437 * Handle the multilanguage URLs
438 */
439 if ($settings ['rewrite_language_url'] == 1 && !$polylangActive) {
440 // Prevent WP to redirect
441 add_filter ( 'redirect_canonical', function ($redirect_url, $requested_url) use ($gpt_supported_languages) {
442 if (defined ( 'GPTRANSLATE_CURRENT_LANG' )) {
443 return false;
444 }
445 }, 10, 2 );
446
447 /**
448 * Add rewrite rules to allow /lang/ URLs
449 */
450 add_filter ( 'rewrite_rules_array', function ($rules) use ($gpt_supported_languages) {
451 $new_rules = [ ];
452 foreach ( $gpt_supported_languages as $lang ) {
453 $new_rules ["{$lang}/(.*)$"] = 'index.php?pagename=$matches[1]&gptlang=' . $lang;
454 $new_rules ["{$lang}$"] = 'index.php?gptlang=' . $lang;
455 }
456 return $new_rules + $rules;
457 } );
458
459 /**
460 * Redirect URLs without language prefix to default language
461 */
462 add_action ( 'template_redirect', function () use ($gpt_supported_languages, $settings) {
463 $defaultOriginalLanguage = get_option ( 'gptranslate_options' ) ['language'] ?? 'en';
464
465 $default_lang = $defaultOriginalLanguage;
466 $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
467
468 // Exceptions and management for subfolder installations
469 if($settings['subfolder_installation']) {
470 // Explode URI into parts
471 $uri_parts = explode('/', ltrim($uri, '/'));
472
473 // Remove the first part (subfolder)
474 array_shift($uri_parts);
475
476 // Rebuild the URI
477 $uri = '/' . implode('/', $uri_parts);
478 }
479
480 // Avoid multiple redirects in the same session
481 if (! session_id ()) {
482 session_start ();
483 }
484
485 if (! empty ( $_SESSION ['gptranslate_redirected'] ) && ($_SESSION ['gptranslate_redirected'] === $uri || $settings ['subfolder_installation'])) {
486 unset ( $_SESSION ['gptranslate_redirected'] );
487 return;
488 }
489
490 // Skip static assets and admin/API routes
491 if (preg_match ( '#\.(ico|png|jpe?g|gif|svg|css|js|woff2?|ttf|eot|mp4|webm)$#i', $uri ) || strpos ( $uri, '/wp-' ) === 0 || strpos ( $uri, '/wp-json/' ) === 0) {
492 return;
493 }
494
495 // If a language has already been detected and defined, skip redirect
496 if (defined ( 'GPTRANSLATE_CURRENT_LANG' )) {
497 return;
498 }
499
500 // Skip the default language if enabled
501 if(isset($settings ['omit_prefix_original_language']) && $settings ['omit_prefix_original_language'] == 1) {
502 return;
503 }
504
505 // Redirect to default language
506 $_SESSION ['gptranslate_redirected'] = $uri;
507 wp_redirect ( home_url ( "/{$default_lang}{$uri}" ), 301 );
508 exit ();
509 } );
510
511 /**
512 * Add gptlang as a recognized query var
513 */
514 add_filter ( 'query_vars', function ($vars) {
515 $vars [] = 'gptlang';
516 return $vars;
517 } );
518
519 /**
520 * Helper function to get current language
521 */
522 function gpt_get_current_lang() {
523 if (defined ( 'GPTRANSLATE_CURRENT_LANG' ))
524 return GPTRANSLATE_CURRENT_LANG;
525 $lang = get_query_var ( 'gptlang' );
526 return $lang ?: 'en';
527 }
528
529 /**
530 * Rewrite internal links to include language prefix
531 */
532 if ($settings ['rewrite_page_links'] == 1 && !$polylangActive) {
533 add_action('template_redirect', function () use ($gpt_supported_languages, $settings) {
534 ob_start(function ($html) use ($gpt_supported_languages, $settings) {
535 $current_lang = defined('GPTRANSLATE_CURRENT_LANG') ? GPTRANSLATE_CURRENT_LANG : ($settings['language'] ?? 'en');
536
537 $site_url = home_url();
538 $parsed_site_url = wp_parse_url($site_url);
539 $base_path = rtrim($parsed_site_url['path'] ?? '', '');
540
541 return preg_replace_callback('#<a\s[^>]*href=["\']([^"\']+)["\']#i', function ($matches) use ($current_lang, $gpt_supported_languages, $parsed_site_url, $base_path, $site_url, $settings) {
542 $original_url = $matches[1];
543 $url = $original_url;
544
545 // Skip image links (lightbox etc.)
546 $image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff'];
547 $lower_url = strtolower(parse_url($url, PHP_URL_PATH));
548 foreach ($image_extensions as $ext) {
549 if (str_ends_with($lower_url, '.' . $ext)) {
550 return $matches[0];
551 }
552 }
553
554 // Skip external URLs
555 if (preg_match('#^https?://#', $url) && strpos($url, $parsed_site_url['host']) === false) {
556 return $matches[0];
557 }
558
559 // Skip admin-related URLs
560 if (strpos($url, 'wp-admin') !== false || strpos($url, 'wp-login.php') !== false || strpos($url, 'wp-json') !== false) {
561 return $matches[0];
562 }
563
564 // Skip adding prefix for default language if enabled
565 $defaultOriginalLanguage = get_option ( 'gptranslate_options' ) ['language'] ?? 'en';
566 if (isset($settings['omit_prefix_original_language']) && $settings['omit_prefix_original_language'] == 1 && $current_lang == $defaultOriginalLanguage) {
567 return $matches[0];
568 }
569
570 // Convert absolute local URL to relative
571 if (strpos($url, $parsed_site_url['scheme'] . '://' . $parsed_site_url['host']) === 0) {
572 $url = substr($url, strlen($parsed_site_url['scheme'] . '://' . $parsed_site_url['host']));
573 }
574
575 // Remove base path if present
576 if (!empty($base_path) && strpos($url, $base_path) === 0) {
577 $url = substr($url, strlen($base_path));
578 }
579
580 // Ensure it starts with /
581 if (strpos($url, '/') !== 0) {
582 return $matches[0]; // ignore non-relative, non-local URLs
583 }
584
585 // Skip if already has a language prefix
586 foreach ($gpt_supported_languages as $lang) {
587 if (preg_match("#^/{$lang}(/|\$)#i", $url)) {
588 return $matches[0];
589 }
590 }
591
592 // Build new full absolute URL
593 $new_url = rtrim($site_url, '/') . '/' . $current_lang . $url;
594
595 return str_replace($original_url, $new_url, $matches[0]);
596
597 }, $html);
598 });
599 }, 15);
600 }
601 }
602 } // end is_admin check
603