diff --git a/projects/plugins/wpcomsh/changelog/fix-theme-translation-files-loading b/projects/plugins/wpcomsh/changelog/fix-theme-translation-files-loading new file mode 100644 index 00000000000..cad28c4c701 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/fix-theme-translation-files-loading @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +I18n: Correct WPCOM locale fallback when theme .mo files use {textdomain}-{locale} names under the themes directory. diff --git a/projects/plugins/wpcomsh/i18n.php b/projects/plugins/wpcomsh/i18n.php index 00fa2fe2266..0b0522b2b93 100644 --- a/projects/plugins/wpcomsh/i18n.php +++ b/projects/plugins/wpcomsh/i18n.php @@ -11,10 +11,11 @@ * * @see p8yzl4-4c-p2 * - * @param string $mofile .mo language file being loaded by load_textdomain(). + * @param string $mofile .mo language file being loaded by load_textdomain(). + * @param string $domain Text domain. * @return string $mofile same or alternate mo file. */ -function wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile ) { +function wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, $domain = '' ) { if ( file_exists( $mofile ) ) { return $mofile; } @@ -27,8 +28,26 @@ function wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile ) { require JETPACK__GLOTPRESS_LOCALES_PATH; } - $locale_slug = basename( $mofile, '.mo' ); - $actual_locale_slug = $locale_slug; + $original_mo_basename = basename( $mofile, '.mo' ); + $locale_slug = $original_mo_basename; + + $theme_root = function_exists( 'get_theme_root' ) + ? wp_normalize_path( trailingslashit( get_theme_root() ) ) + : ''; + + // On WP Cloud sites, get_template_directory() returns /wordpress/themes/pub... + // which is the actual path to the theme, symlinked from wp-content/themes. + // _load_textdomain_just_in_time sees that the folders don't match + // and builds the mo file as "{$path}{$domain}-{$locale}.mo", so we need to + // remove the domain prefix to get the locale. + $remove_domain_prefix_for_theme = is_string( $domain ) && '' !== $domain + && '' !== $theme_root + && str_starts_with( wp_normalize_path( $mofile ), $theme_root ) + && str_starts_with( $original_mo_basename, $domain . '-' ); + + if ( $remove_domain_prefix_for_theme ) { + $locale_slug = str_replace( $domain . '-', '', $original_mo_basename ); + } // These locales are not in our GP_Locales file, so rewrite them. $locale_mappings = array( @@ -56,10 +75,10 @@ function wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile ) { } // phpcs:ignore WordPress.PHP.PregQuoteDelimiter.Missing - $mofile = preg_replace( '/' . preg_quote( $actual_locale_slug ) . '\.mo$/', $locale_slug . '.mo', $mofile ); + $mofile = preg_replace( '/' . preg_quote( $original_mo_basename ) . '\.mo$/', $locale_slug . '.mo', $mofile ); return $mofile; } -add_filter( 'load_textdomain_mofile', 'wpcomsh_wporg_to_wpcom_locale_mo_file', 9999 ); +add_filter( 'load_textdomain_mofile', 'wpcomsh_wporg_to_wpcom_locale_mo_file', 9999, 2 ); // Load translations for wpcomsh itself via MO file. add_action( diff --git a/projects/plugins/wpcomsh/tests/I18nTest.php b/projects/plugins/wpcomsh/tests/I18nTest.php new file mode 100644 index 00000000000..9b92b250b84 --- /dev/null +++ b/projects/plugins/wpcomsh/tests/I18nTest.php @@ -0,0 +1,85 @@ +markTestSkipped( 'Jetpack compat locales file not found: ' . $locales ); + } + define( 'JETPACK__GLOTPRESS_LOCALES_PATH', $locales ); + } + } + + /** + * When the .mo file already exists, the path must not be rewritten. + */ + public function test_returns_unchanged_when_mo_file_exists() { + $tmp = tempnam( sys_get_temp_dir(), 'wpcomsh-i18n-' ); + $this->assertNotFalse( $tmp ); + $mofile = $tmp . '.mo'; + $this->assertTrue( rename( $tmp, $mofile ) ); + + $result = wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, 'any-domain' ); + $this->assertSame( $mofile, $result ); + + $this->assertTrue( unlink( $mofile ) ); + } + + public function test_rewrites_domain_prefixed_theme_mo_nb_no_to_no_slug() { + $theme_root = trailingslashit( wp_normalize_path( get_theme_root() ) ); + $mofile = $theme_root . 'faketheme/languages/faketheme-nb_NO.mo'; + $this->assertFalse( file_exists( $mofile ) ); + + $result = wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, 'faketheme' ); + + $this->assertSame( $theme_root . 'faketheme/languages/no.mo', $result ); + } + + public function test_rewrites_domain_prefixed_theme_mo_de_de_formal() { + $theme_root = trailingslashit( wp_normalize_path( get_theme_root() ) ); + $mofile = $theme_root . 'faketheme/languages/faketheme-de_DE_formal.mo'; + $this->assertFalse( file_exists( $mofile ) ); + + $result = wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, 'faketheme' ); + + $this->assertSame( $theme_root . 'faketheme/languages/de.mo', $result ); + } + + public function test_rewrites_unprefixed_theme_mo_de_de_to_de_slug() { + $theme_root = trailingslashit( wp_normalize_path( get_theme_root() ) ); + $mofile = $theme_root . 'faketheme/languages/de_DE.mo'; + $this->assertFalse( file_exists( $mofile ) ); + + $result = wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, 'faketheme' ); + + $this->assertSame( $theme_root . 'faketheme/languages/de.mo', $result ); + } + + /** + * Paths outside get_theme_root() do not strip {domain}- from the basename; unknown wp_locale leaves path unchanged. + */ + public function test_does_not_rewrite_plugin_style_path_with_domain_like_basename() { + $mofile = trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ) . 'some-plugin/languages/some-plugin-nb_NO.mo'; + $this->assertFalse( file_exists( $mofile ) ); + + $result = wpcomsh_wporg_to_wpcom_locale_mo_file( $mofile, 'some-plugin' ); + + $this->assertSame( $mofile, $result ); + } +}