Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/packages/agents-manager/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ These two are independent: block-editor enablement does not imply the unified ex
## Cross-Repo Relationship

- All JS/CSS bundles are fetched from `widgets.wp.com/agents-manager/`, built by the Calypso `apps/agents-manager/` app.
- Translations are loaded from `widgets.wp.com/agents-manager/languages/{locale}-v1.js` (the same directory Image Studio uses, since it ships from the same Calypso app). The locale is normalised to an ISO 639 code by `determine_iso_639_locale()`. Skipped for English and for disconnected variants, mirroring Help Center.
- Asset metadata (`.asset.json`) is fetched via HTTP on Atomic sites or read from disk on Simple sites, then cached in a transient for 1 hour.
- The REST endpoint proxies to `/agents-manager/state` on wpcom via `Jetpack\Connection\Client::wpcom_json_api_request_as_user()`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Load translation files for the Agents Manager UI so it can be displayed in the user's language.
46 changes: 46 additions & 0 deletions projects/packages/agents-manager/src/class-agents-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,25 @@ private function enqueue_script( $variant ) {

$script_dependencies = $asset_file['dependencies'] ?? array();

// Load translations for connected variants from widgets.wp.com.
// Disconnected variants have no translatable UI, so skip them (as Help
// Center does). English needs no translation file.
if ( ! str_contains( $variant, 'disconnected' ) ) {
$locale = self::determine_iso_639_locale();
Comment thread
t-wright marked this conversation as resolved.

if ( 'en' !== $locale ) {
wp_enqueue_script(
'agents-manager-translations',
'https://widgets.wp.com/agents-manager/languages/' . $locale . '-v1.js',
array( 'wp-i18n' ),
$version,
true
);

$script_dependencies[] = 'agents-manager-translations';
}
}

wp_enqueue_script(
'agents-manager',
'https://widgets.wp.com/agents-manager/agents-manager-' . $variant . '.min.js',
Expand All @@ -543,6 +562,33 @@ private function enqueue_script( $variant ) {
}
}

/**
* Returns the ISO 639 conforming locale string for the current user.
*
* Normalizes the WordPress user locale to match the widgets.wp.com translation
* file naming at languages/{code}-v1.js. Preserves the region for the few locales
* where it is meaningful (pt-br, zh-tw, zh-cn); strips the region for all others;
* falls back to 'en' when the locale is empty.
*
* @return string The ISO 639 locale string, e.g. "en".
*/
private static function determine_iso_639_locale() {
$language = get_user_locale();
$language = strtolower( $language );

if ( in_array( $language, array( 'pt_br', 'pt-br', 'zh_tw', 'zh-tw', 'zh_cn', 'zh-cn' ), true ) ) {
$language = str_replace( '_', '-', $language );
} else {
$language = preg_replace( '/([-_].*)$/i', '', $language );
}

if ( empty( $language ) ) {
return 'en';
}

return $language;
}

/**
* Get the asset via file-system on wpcom and via network on Atomic sites.
*
Expand Down
179 changes: 179 additions & 0 deletions projects/packages/agents-manager/tests/php/Agents_Manager_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Automattic\Jetpack\Status\Cache;
use Brain\Monkey\Functions;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;

require_once __DIR__ . '/../../src/class-agents-manager.php';

Expand Down Expand Up @@ -2468,4 +2469,182 @@ public function test_should_enqueue_script_returns_false_on_woocommerce_admin_ho

$this->assertFalse( $result, 'should_enqueue_script should return false on WooCommerce Admin home page' );
}

/**
* Helper to call the private determine_iso_639_locale method via reflection.
*
* @return string The resolved ISO 639 locale code.
*/
private function call_determine_iso_639_locale() {
$reflection = new \ReflectionClass( Agents_Manager::class );
$method = $reflection->getMethod( 'determine_iso_639_locale' );
if ( PHP_VERSION_ID < 80100 ) {
$method->setAccessible( true );
}
return $method->invoke( null );
}

/**
* Tests that the translation script is enqueued for a connected variant when the
* user's locale is not English, and that the main script depends on it so the
* translations load first.
*/
public function test_translations_enqueued_for_non_english_locale() {
$this->set_admin_context();
global $wp_scripts;
$wp_scripts = null;

// Pre-seed the asset manifest transient so enqueue_script does not hit the network.
set_transient(
'agents-manager-asset-wp-admin.asset.json',
array(
'version' => '1.2.3',
'dependencies' => array(),
),
HOUR_IN_SECONDS
);

$locale_filter = static function () {
return 'de_DE';
};
add_filter( 'locale', $locale_filter );

// Enable unified experience. Not a Jetpack site, so the connected wp-admin variant is used.
add_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );

$this->agents_manager->enqueue_scripts();

$this->assertNotNull( $wp_scripts, 'wp_scripts should be initialized' );
$this->assertTrue(
wp_script_is( 'agents-manager-translations', 'enqueued' ),
'Translation script should be enqueued for a non-English locale'
);
$this->assertSame(
'https://widgets.wp.com/agents-manager/languages/de-v1.js',
$wp_scripts->registered['agents-manager-translations']->src,
'Translation script should point at the agents-manager languages directory for the resolved locale'
);
$this->assertContains(
'agents-manager-translations',
$wp_scripts->registered['agents-manager']->deps,
'Main script should depend on the translation script so it loads first'
);

remove_filter( 'locale', $locale_filter );
remove_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );
delete_transient( 'agents-manager-asset-wp-admin.asset.json' );
}

/**
* Tests that no translation script is enqueued when the user's locale is English,
* since the English strings ship in the bundle itself.
*/
public function test_translations_not_enqueued_for_english_locale() {
$this->set_admin_context();
global $wp_scripts;
$wp_scripts = null;

set_transient(
'agents-manager-asset-wp-admin.asset.json',
array(
'version' => '1.2.3',
'dependencies' => array(),
),
HOUR_IN_SECONDS
);

$locale_filter = static function () {
return 'en_US';
};
add_filter( 'locale', $locale_filter );
add_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );

$this->agents_manager->enqueue_scripts();

$this->assertFalse(
wp_script_is( 'agents-manager-translations', 'enqueued' ),
'Translation script should not be enqueued for an English locale'
);

remove_filter( 'locale', $locale_filter );
remove_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );
delete_transient( 'agents-manager-asset-wp-admin.asset.json' );
}

/**
* Tests that translations are not loaded for disconnected variants, whose minimal
* bundles have no translatable in-app UI. Mirrors Help Center's behavior.
*/
public function test_translations_not_enqueued_for_disconnected_variant() {
$this->set_admin_context();
global $wp_scripts;
$wp_scripts = null;

set_transient(
'agents-manager-asset-wp-admin-disconnected.asset.json',
array(
'version' => '1.2.3',
'dependencies' => array(),
),
HOUR_IN_SECONDS
);

$locale_filter = static function () {
return 'de_DE';
};
add_filter( 'locale', $locale_filter );

// Unified experience on a Jetpack site with a disconnected user yields wp-admin-disconnected.
add_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );
add_filter( 'is_jetpack_site', '__return_true', 20 );

$this->agents_manager->enqueue_scripts();

$this->assertFalse(
wp_script_is( 'agents-manager-translations', 'enqueued' ),
'Translation script should not be enqueued for the wp-admin-disconnected variant'
);

remove_filter( 'locale', $locale_filter );
remove_filter( 'agents_manager_use_unified_experience', '__return_true', 20 );
remove_filter( 'is_jetpack_site', '__return_true', 20 );
delete_transient( 'agents-manager-asset-wp-admin-disconnected.asset.json' );
}

/**
* Tests that determine_iso_639_locale normalizes WordPress locales to the ISO 639
* codes used by the widgets.wp.com translation files.
*
* @param string $wp_locale The WordPress user locale.
* @param string $expected The expected normalized ISO 639 code.
* @dataProvider provide_locales
*/
#[DataProvider( 'provide_locales' )]
public function test_determine_iso_639_locale_normalizes_locales( $wp_locale, $expected ) {
$locale_filter = static function () use ( $wp_locale ) {
return $wp_locale;
};
add_filter( 'locale', $locale_filter );

$this->assertSame( $expected, $this->call_determine_iso_639_locale() );

remove_filter( 'locale', $locale_filter );
}

/**
* Data provider for locale normalization.
*
* @return array<string, array{0: string, 1: string}>
*/
public static function provide_locales() {
return array(
'German strips region' => array( 'de_DE', 'de' ),
'French strips region' => array( 'fr_FR', 'fr' ),
'Brazilian Portuguese kept' => array( 'pt_BR', 'pt-br' ),
'Traditional Chinese kept' => array( 'zh_TW', 'zh-tw' ),
'Simplified Chinese kept' => array( 'zh_CN', 'zh-cn' ),
'English strips region' => array( 'en_US', 'en' ),
'Empty locale falls back to en' => array( '', 'en' ),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: other

Image Studio: share the translation script handle with the Agents Manager package to avoid enqueuing the same file twice.
Comment thread
t-wright marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -385,16 +385,18 @@ function do_enqueue_assets() {
$locale = determine_iso_639_locale();

if ( 'en' !== $locale ) {
// Load translations from widgets.wp.com.
// Shared handle with the Agents Manager package, which enqueues the same
// file. Both register it, so WordPress de-duplicates when both load and
// each still works on its own.
wp_enqueue_script(
'image-studio-translations',
'agents-manager-translations',
ASSET_TRANSLATIONS_URL . $locale . '-v1.js',
array( 'wp-i18n' ),
$version,
true
);

$dependencies[] = 'image-studio-translations';
$dependencies[] = 'agents-manager-translations';
}

wp_enqueue_script(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1557,10 +1557,10 @@ function () {
);
$this->enable_and_enqueue_block_editor();

$this->assertFalse( wp_script_is( 'image-studio-translations', 'enqueued' ) );
$this->assertFalse( wp_script_is( 'agents-manager-translations', 'enqueued' ) );

$script = $GLOBALS['wp_scripts']->registered[ ImageStudio\FEATURE_NAME ];
$this->assertNotContains( 'image-studio-translations', $script->deps );
$this->assertNotContains( 'agents-manager-translations', $script->deps );
}

/**
Expand All @@ -1576,14 +1576,14 @@ function () {
);
$this->enable_and_enqueue_block_editor();

$this->assertTrue( wp_script_is( 'image-studio-translations', 'enqueued' ) );
$this->assertTrue( wp_script_is( 'agents-manager-translations', 'enqueued' ) );

$tr_script = $GLOBALS['wp_scripts']->registered['image-studio-translations'];
$tr_script = $GLOBALS['wp_scripts']->registered['agents-manager-translations'];
$this->assertStringContainsString( 'languages/fr-v1.js', $tr_script->src );
$this->assertContains( 'wp-i18n', $tr_script->deps );

$main_script = $GLOBALS['wp_scripts']->registered[ ImageStudio\FEATURE_NAME ];
$this->assertContains( 'image-studio-translations', $main_script->deps );
$this->assertContains( 'agents-manager-translations', $main_script->deps );
}

/**
Expand All @@ -1598,7 +1598,7 @@ function () {
);
$this->enable_and_enqueue_block_editor();

$script = $GLOBALS['wp_scripts']->registered['image-studio-translations'];
$script = $GLOBALS['wp_scripts']->registered['agents-manager-translations'];
$this->assertStringContainsString( 'languages/pt-br-v1.js', $script->src );
}

Expand Down
Loading