# wplingua/2.14.1/inc/i18n-script.php

Translate and Go multilingual – Automatic AI translation – wpLingua, version 2.14.1. 381 lines.

- Page: https://pluginprobe.com/plugins/wplingua/2.14.1/code/inc/i18n-script.php
- Raw: https://pluginprobe.com/plugins/wplingua/2.14.1/raw/inc/i18n-script.php
- Modified: 2026-06-04T00:09:52+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/wplingua/2.14.1/code/inc/i18n-script.php#L10-L20`.

```php
<?php

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}


/**
 * Load or generate a translation JSON for a registered script.
 *
 * Checks whether wpLingua should provide a replacement wp-i18n JSON file for
 * the given script. If needed, reads the original JS, extracts wp-i18n strings,
 * builds a translations JSON and writes a cached file that WordPress can use.
 *
 * Behavior:
 *  - Skips processing in admin or when the default file is already readable.
 *  - Maps registered scripts that depend on 'wp-i18n' and live under wp-content.
 *  - Returns either the original $file, a generated cached JSON path, or an
 *    empty placeholder file path when no strings are found.
 *
 * @param string $file   Path to the default translation file provided by WP.
 * @param string $handle Registered script handle.
 * @param string $domain Text domain passed by WordPress.
 * @return string Path to the translation JSON file to use.
 */
function wplng_load_script_translation_file( $file, $handle, $domain ) {

	global $wplng_i18n_scripts;

	/**
	 * If we are in the admin dashboard
	 * or it's not a translated page
	 * or if the translation file was generate by wp, plugin or theme
	 * Use the default file
	 */

	if ( is_admin()
		|| wplng_get_language_website_id() === wplng_get_language_current_id()
		|| empty( $file )
		|| is_readable( $file )
	) {
		return $file;
	}

	/**
	 * Initialize $wplng_i18n_scripts if not already initialized
	 */

	if ( $wplng_i18n_scripts === null ) {

		$wp_scripts = wp_scripts();

		if ( ! $wp_scripts instanceof WP_Scripts ) {
			return $file;
		}

		$wplng_i18n_scripts = array();

		foreach ( $wp_scripts->registered as $handle_temp => $script_object ) {

			if ( in_array( 'wp-i18n', (array) $script_object->deps, true )
				&& wplng_str_contains( $script_object->src, '/wp-content/' )
			) {

				$wplng_i18n_scripts[ $handle_temp ] = array(
					'handle'     => $script_object->handle,
					'textdomain' => $script_object->textdomain,
					'src'        => $script_object->src,
				);

			}
		}
	}

	/**
	 * If we can not associate the current script to a knowed script
	 * (We can not know the script JS file, return)
	 */

	if ( empty( $wplng_i18n_scripts ) || empty( $wplng_i18n_scripts[ $handle ] ) ) {
		return $file;
	}

	/**
	 * Check if the replacement translation JSON was already generated by wpLingua
	 */

	$file_nomalized   = wp_normalize_path( $file );
	$dir_cache_script = '/script-i18n';

	$file_cache_relative = str_replace(
		wp_normalize_path( WP_CONTENT_DIR ),
		'',
		$file_nomalized
	);

	if ( $file_nomalized === $file_cache_relative ) {
		return $file;
	}

	$file_cache_absolute = WPLNG_CACHE_DIR . $dir_cache_script . $file_cache_relative;

	if ( is_readable( $file_cache_absolute ) ) {
		return $file_cache_absolute;
	}

	/**
	 * Get the original script path and content
	 */

	$script_path = str_replace(
		content_url(),
		WP_CONTENT_DIR,
		$wplng_i18n_scripts[ $handle ]['src']
	);

	$script_path = wp_normalize_path( $script_path );

	if ( ! is_readable( $script_path ) ) {
		return $file;
	}

	// Skip files larger than 2MB to avoid memory issues
	if ( filesize( $script_path ) > 2 * 1024 * 1024 ) {
		return $file;
	}

	$script_content = file_get_contents( $script_path );

	/**
	 * Get texts in script
	 */

	$texts = wplng_i18n_script_extract_strings( $script_content );

	$texts_is_empty = true;

	foreach ( $texts as $texts_by_extraction_methode ) {
		if ( ! empty( $texts_by_extraction_methode ) ) {
			$texts_is_empty = false;
			break;
		}
	}

	if ( $texts_is_empty ) {

		wplng_put_cache_file(
			'/script-i18n' . $file_cache_relative,
			''
		);

		return $file;
	}

	/**
	 * Make the translations JSON
	 */

	$json_content = wplng_i18n_script_generate_json(
		$texts,
		$domain
	);

	// If JSON generation failed, write empty sentinel and return original file
	if ( '' === $json_content ) {
		wplng_put_cache_file(
			'/script-i18n' . $file_cache_relative,
			''
		);
		return $file;
	}

	/**
	 * Generate the wpLingua cached JSON file
	 */

	$file_writing_result = wplng_put_cache_file(
		'/script-i18n' . $file_cache_relative,
		$json_content
	);

	// Return the generated file if writing was successful
	if ( $file_writing_result !== false ) {
		return $file_cache_absolute;
	}

	return $file;
}


/**
 * Extract translatable strings from a JavaScript script.
 *
 * Scans minified WP JS for wp-i18n call patterns used in builds like:
 *   (0,x.__)("text")
 *   (0,x._x)("text","context")
 *   (0,x._n)("singular","plural",count)
 *   (0,x._nx)("singular","plural",count,"context")
 *
 * The function handles both single and double quoted strings, escaped characters,
 * optional domain arguments and returns an associative array grouping extracted
 * items by function name ('__', '_x', '_n', '_nx').
 *
 * @param string $script JavaScript source code to scan.
 * @return array {
 *   Associative array with keys:
 *     '__'  => array of [ 'text' => string, 'domain' => ?string ],
 *     '_x'  => array of [ 'text' => string, 'context' => string, 'domain' => ?string ],
 *     '_n'  => array of [ 'singular' => string, 'plural' => string, 'domain' => ?string ],
 *     '_nx' => array of [ 'singular' => string, 'plural' => string, 'context' => string, 'domain' => ?string ],
 * }
 */
function wplng_i18n_script_extract_strings( $script ) {

	$results = array(
		'__'  => array(),
		'_x'  => array(),
		'_n'  => array(),
		'_nx' => array(),
	);

	// In WordPress minified scripts, calls look like:
	// (0,r.__)("text") or (0,t._x)("text","context")
	// where r/t/e/n are aliases for wp.i18n

	$patterns = array(
		// (0,X.__)("text") or (0,X.__)('text') with optional domain
		'__'  => '/\(0,[a-zA-Z_$][a-zA-Z0-9_$]*\.__\)\s*\(\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\1)["\'])*)\1(?:\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\3)["\'])*)\3)?\s*\)/u',

		// (0,X._x)("text","context") or with single quotes, optional domain
		'_x'  => '/\(0,[a-zA-Z_$][a-zA-Z0-9_$]*\._x\)\s*\(\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\1)["\'])*)\1\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\3)["\'])*)\3(?:\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\5)["\'])*)\5)?\s*\)/u',

		// (0,X._n)("singular","plural",number) with optional domain
		'_n'  => '/\(0,[a-zA-Z_$][a-zA-Z0-9_$]*\._n\)\s*\(\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\1)["\'])*)\1\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\3)["\'])*)\3\s*,\s*[^,)]+(?:\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\5)["\'])*)\5)?\s*\)/u',

		// (0,X._nx)("singular","plural",number,"context") with optional domain
		'_nx' => '/\(0,[a-zA-Z_$][a-zA-Z0-9_$]*\._nx\)\s*\(\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\1)["\'])*)\1\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\3)["\'])*)\3\s*,\s*[^,]+\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\5)["\'])*)\5(?:\s*,\s*(["\'])((?:[^"\'\\\\]|\\\\.|(?!\7)["\'])*)\7)?\s*\)/u',
	);

	// Extraction for __()
	if ( preg_match_all( $patterns['__'], $script, $matches, PREG_SET_ORDER ) ) {
		foreach ( $matches as $match ) {
			$text = wplng_unescape_js_string( $match[2] );
			if ( ! empty( $text ) ) {
				$results['__'][] = array(
					'text'   => $text,
					'domain' => isset( $match[4] ) && $match[4] !== '' ? wplng_unescape_js_string( $match[4] ) : null,
				);
			}
		}
	}

	// Extraction for _x()
	if ( preg_match_all( $patterns['_x'], $script, $matches, PREG_SET_ORDER ) ) {
		foreach ( $matches as $match ) {
			$text = wplng_unescape_js_string( $match[2] );
			if ( ! empty( $text ) ) {
				$results['_x'][] = array(
					'text'    => $text,
					'context' => wplng_unescape_js_string( $match[4] ),
					'domain'  => isset( $match[6] ) && $match[6] !== '' ? wplng_unescape_js_string( $match[6] ) : null,
				);
			}
		}
	}

	// Extraction for _n()
	if ( preg_match_all( $patterns['_n'], $script, $matches, PREG_SET_ORDER ) ) {
		foreach ( $matches as $match ) {
			$singular = wplng_unescape_js_string( $match[2] );
			if ( ! empty( $singular ) ) {
				$results['_n'][] = array(
					'singular' => $singular,
					'plural'   => wplng_unescape_js_string( $match[4] ),
					'domain'   => isset( $match[6] ) && $match[6] !== '' ? wplng_unescape_js_string( $match[6] ) : null,
				);
			}
		}
	}

	// Extraction for _nx()
	if ( preg_match_all( $patterns['_nx'], $script, $matches, PREG_SET_ORDER ) ) {
		foreach ( $matches as $match ) {
			$singular = wplng_unescape_js_string( $match[2] );
			if ( ! empty( $singular ) ) {
				$results['_nx'][] = array(
					'singular' => $singular,
					'plural'   => wplng_unescape_js_string( $match[4] ),
					'context'  => wplng_unescape_js_string( $match[6] ),
					'domain'   => isset( $match[8] ) && $match[8] !== '' ? wplng_unescape_js_string( $match[8] ) : null,
				);
			}
		}
	}

	return $results;
}


/**
 * Generates a WordPress wp-i18n compatible translation JSON
 *
 * @param array  $texts   The texts extracted by wplng_i18n_script_extract_strings()
 * @param string $domain  The text domain
 * @param string $locale  The locale (e.g., 'fr_FR')
 * @param string $script_path Path to the JS script (for reference)
 * @return string Encoded JSON
 */
function wplng_i18n_script_generate_json( $texts, $domain = 'messages' ) {

	$locale = get_locale();

	// Determine the plural form based on locale
	$plural_forms = wplng_get_language_plural_forms( $locale );

	// Build the messages array
	$messages = array(
		'' => array(
			'domain'       => $domain,
			'plural-forms' => $plural_forms,
			'lang'         => str_replace( '_', '-', $locale ),
		),
	);

	// Add __() translations
	foreach ( $texts['__'] as $item ) {
		$key         = $item['text'];
		$text_domain = ! empty( $item['domain'] ) ? $item['domain'] : $domain;
		// Value = array with the translation
		$messages[ $key ] = array( __( $item['text'], $text_domain ) );
	}

	// Add _x() translations with context
	foreach ( $texts['_x'] as $item ) {
		// Key with context: "text\u0004context"
		$key              = $item['text'] . "\x04" . $item['context'];
		$text_domain      = ! empty( $item['domain'] ) ? $item['domain'] : $domain;
		$messages[ $key ] = array( _x( $item['text'], $item['context'], $text_domain ) );
	}

	// Add _n() translations (plurals)
	foreach ( $texts['_n'] as $item ) {
		$key         = $item['singular'];
		$text_domain = ! empty( $item['domain'] ) ? $item['domain'] : $domain;
		// Array with [translated singular, translated plural]
		$messages[ $key ] = array(
			__( $item['singular'], $text_domain ),
			__( $item['plural'], $text_domain ),
		);
	}

	// Add _nx() translations (plurals with context)
	foreach ( $texts['_nx'] as $item ) {
		$key              = $item['singular'] . "\x04" . $item['context'];
		$text_domain      = ! empty( $item['domain'] ) ? $item['domain'] : $domain;
		$messages[ $key ] = array(
			_x( $item['singular'], $item['context'], $text_domain ),
			_x( $item['plural'], $item['context'], $text_domain ),
		);
	}

	// Build the complete structure
	$json_data = array(
		'translation-revision-date' => gmdate( 'Y-m-d H:i:s+0000' ),
		'generator'                 => 'wpLingua',
		'domain'                    => $domain,
		'locale_data'               => array(
			'messages' => $messages,
		),
	);

	$encoded = wp_json_encode( $json_data, JSON_UNESCAPED_UNICODE );

	if ( false === $encoded ) {
		return '';
	}

	return $encoded;
}

```
