diff --git src/js/_enqueues/wp/customize/controls.js src/js/_enqueues/wp/customize/controls.js
index 6c92362410..0a86284f2c 100644
--- src/js/_enqueues/wp/customize/controls.js
+++ src/js/_enqueues/wp/customize/controls.js
@@ -2117,7 +2117,9 @@
 							}
 						});
 						if ( 'local' !== section.params.filter_type ) {
-							wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
+							wp.a11y.speak(
+								wp.i18n.sprintf( wp.i18n._n( '%d theme found', '%d themes found', data.info.results ), data.info.results )
+							);
 						}
 					}
 
@@ -2421,7 +2423,7 @@
 		 * @return {void}
 		 */
 		updateCount: function( count ) {
-			var section = this, countEl, displayed;
+			var section = this, i18n = wp.i18n, countEl, countText, displayed;
 
 			if ( ! count && 0 !== count ) {
 				count = section.getVisibleCount();
@@ -2429,17 +2431,20 @@
 
 			displayed = section.contentContainer.find( '.themes-displayed' );
 			countEl = section.contentContainer.find( '.theme-count' );
+			countText = i18n.sprintf( i18n._n( '%s theme', '%s themes', count ), count );
 
 			if ( 0 === count ) {
-				countEl.text( '0' );
+				countEl.text( countText );
 			} else {
 
 				// Animate the count change for emphasis.
 				displayed.fadeOut( 180, function() {
-					countEl.text( count );
+					countEl.text( countText );
 					displayed.fadeIn( 180 );
 				} );
-				wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) );
+				wp.a11y.speak(
+					i18n.sprintf( i18n._n( 'Displaying %d theme', 'Displaying %d themes', count ), count )
+				);
 			}
 		},
 
@@ -5480,11 +5485,15 @@
 			control.setting.notifications.remove( 'csslint_error' );
 
 			if ( 0 !== errorAnnotations.length ) {
-				if ( 1 === errorAnnotations.length ) {
-					message = api.l10n.customCssError.singular.replace( '%d', '1' );
-				} else {
-					message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
-				}
+				message = wp.i18n.sprintf(
+					wp.i18n._n(
+						'There is %d error which must be fixed before you can save.',
+						'There are %d errors which must be fixed before you can save.',
+						errorAnnotations.length
+					),
+					errorAnnotations.length
+				)
+
 				control.setting.notifications.add( new api.Notification( 'csslint_error', {
 					message: message,
 					type: 'error'
@@ -7502,7 +7511,14 @@
 
 						if ( invalidSettings.length ) {
 							api.notifications.add( new api.Notification( errorCode, {
-								message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ),
+								message: wp.i18n.sprintf(
+									wp.i18n._n(
+										'Unable to save due to %s invalid setting.',
+										'Unable to save due to %s invalid settings.',
+										invalidSettings.length
+									),
+									invalidSettings.length
+								),
 								type: 'error',
 								dismissible: true,
 								saveFailure: true
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index decf0b87cf..0e29d3b337 100644
--- src/wp-includes/class-wp-customize-manager.php
+++ src/wp-includes/class-wp-customize-manager.php
@@ -4865,15 +4865,17 @@ final class WP_Customize_Manager {
 			'previewableDevices'     => $this->get_previewable_devices(),
 			'l10n'                   => array(
 				'confirmDeleteTheme'   => __( 'Are you sure you want to delete this theme?' ),
-				/* translators: %d: Number of theme search results, which cannot currently consider singular vs. plural forms. */
-				'themeSearchResults'   => __( '%d themes found' ),
-				/* translators: %d: Number of themes being displayed, which cannot currently consider singular vs. plural forms. */
-				'announceThemeCount'   => __( 'Displaying %d themes' ),
 				/* translators: %s: Theme name. */
 				'announceThemeDetails' => __( 'Showing details for theme: %s' ),
 			),
 		);
 
+		// These strings are here for backwards compatibility; the translations now occur in JavaScript.
+		/* translators: %d: Number of theme search results. Note there is a newer translation of this string with singular and plural forms. */
+		$settings['l10n']['themeSearchResults'] = __( '%d themes found' );
+		/* translators: %d: Number of themes being displayed. Note there is a newer translation of this string with singular and plural forms. */
+		$settings['l10n']['announceThemeCount'] = __( 'Displaying %d themes' );
+
 		// Temporarily disable installation in Customizer. See #42184.
 		$filesystem_method = get_filesystem_method();
 		ob_start();
diff --git src/wp-includes/customize/class-wp-customize-themes-section.php src/wp-includes/customize/class-wp-customize-themes-section.php
index 14db083e70..f0f365f5d1 100644
--- src/wp-includes/customize/class-wp-customize-themes-section.php
+++ src/wp-includes/customize/class-wp-customize-themes-section.php
@@ -147,10 +147,12 @@ class WP_Customize_Themes_Section extends WP_Customize_Section {
 		<# } #>
 		<div class="filter-themes-count">
 			<span class="themes-displayed">
-				<?php
-				/* translators: %s: Number of themes displayed. */
-				echo sprintf( __( '%s themes' ), '<span class="theme-count">0</span>' );
-				?>
+				<span class="theme-count">
+					<?php
+					/* translators: %s: Number of themes displayed. */
+					printf( _n( '%s theme', '%s themes', 0 ), 0 );
+					?>
+				</span>
 			</span>
 		</div>
 		<?php
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
index 5452ff0848..343395b04f 100644
--- src/wp-includes/script-loader.php
+++ src/wp-includes/script-loader.php
@@ -1520,7 +1520,7 @@ function wp_default_scripts( &$scripts ) {
 	$scripts->add( 'customize-preview', "/wp-includes/js/customize-preview$suffix.js", array( 'wp-a11y', 'customize-base' ), false, 1 );
 	$scripts->add( 'customize-models', '/wp-includes/js/customize-models.js', array( 'underscore', 'backbone' ), false, 1 );
 	$scripts->add( 'customize-views', '/wp-includes/js/customize-views.js', array( 'jquery', 'underscore', 'imgareaselect', 'customize-models', 'media-editor', 'media-views' ), false, 1 );
-	$scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util', 'jquery-ui-core' ), false, 1 );
+	$scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-i18n', 'wp-util', 'jquery-ui-core' ), false, 1 );
 	did_action( 'init' ) && $scripts->localize(
 		'customize-controls',
 		'_wpCustomizeControlsL10n',
@@ -1563,19 +1563,7 @@ function wp_default_scripts( &$scripts ) {
 			'videoHeaderNotice'       => __( 'This theme doesn&#8217;t support video headers on this page. Navigate to the front page or another page that supports video headers.' ),
 			// Used for overriding the file types allowed in plupload.
 			'allowedFiles'            => __( 'Allowed Files' ),
-			'customCssError'          => array(
-				/* translators: %d: Error count. */
-				'singular' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 1 ),
-				/* translators: %d: Error count. */
-				'plural'   => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 2 ), // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491.
-			),
 			'pageOnFrontError'        => __( 'Homepage and posts page must be different.' ),
-			'saveBlockedError'        => array(
-				/* translators: %s: Number of invalid settings. */
-				'singular' => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 1 ),
-				/* translators: %s: Number of invalid settings. */
-				'plural'   => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 2 ), // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491.
-			),
 			'scheduleDescription'     => __( 'Schedule your customization changes to publish ("go live") at a future date.' ),
 			'themePreviewUnavailable' => __( 'Sorry, you can&#8217;t preview new themes when you have changes scheduled or saved as a draft. Please publish your changes, or wait until they publish to preview new themes.' ),
 			'themeInstallUnavailable' => sprintf(
@@ -1586,6 +1574,19 @@ function wp_default_scripts( &$scripts ) {
 			'publishSettings'         => __( 'Publish Settings' ),
 			'invalidDate'             => __( 'Invalid date.' ),
 			'invalidValue'            => __( 'Invalid value.' ),
+			// These strings are here for backward compatibility; the translations now occurs in JavaScript.
+			'customCssError'          => array(
+				/* translators: %d: Error count. */
+				'singular' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 1 ),
+				/* translators: %d: Error count. */
+				'plural'   => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 2 ),
+			),
+			'saveBlockedError'        => array(
+				/* translators: %s: Number of invalid settings. */
+				'singular' => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 1 ),
+				/* translators: %s: Number of invalid settings. */
+				'plural'   => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 2 ),
+			),
 		)
 	);
 	$scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
