Index: src/js/_enqueues/wp/customize/controls.js
===================================================================
--- src/js/_enqueues/wp/customize/controls.js	(revision 50342)
+++ src/js/_enqueues/wp/customize/controls.js	(working copy)
@@ -2119,7 +2119,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 )
+							);
 						}
 					}
 
@@ -2426,7 +2428,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();
@@ -2434,17 +2436,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 )
+				);
 			}
 		},
 
@@ -5488,11 +5493,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'
@@ -7515,7 +7524,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
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 50342)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -4901,15 +4901,17 @@
 			'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();
Index: src/wp-includes/customize/class-wp-customize-themes-section.php
===================================================================
--- src/wp-includes/customize/class-wp-customize-themes-section.php	(revision 50342)
+++ src/wp-includes/customize/class-wp-customize-themes-section.php	(working copy)
@@ -147,10 +147,12 @@
 		<# } #>
 		<div class="filter-themes-count">
 			<span class="themes-displayed">
+			<span class="theme-count">
 				<?php
 				/* translators: %s: Number of themes displayed. */
-				printf( __( '%s themes' ), '<span class="theme-count">0</span>' );
+				printf( _n( '%s theme', '%s themes', '{{ data.count }}' ), '{{ data.count }}' );
 				?>
+				</span>
 			</span>
 		</div>
 		<?php
Index: src/wp-includes/script-loader.php
===================================================================
--- src/wp-includes/script-loader.php	(revision 50342)
+++ src/wp-includes/script-loader.php	(working copy)
@@ -1141,7 +1141,7 @@
 	$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',
@@ -1184,31 +1184,30 @@
 			'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' ),
+			'pageOnFrontError'        => __( 'Homepage and posts page must be different.' ),
+			'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(
+				/* translators: %s: URL to Add Themes admin screen. */
+				__( 'You won&#8217;t be able to install new themes from here yet since your install requires SFTP credentials. For now, please <a href="%s">add themes in the admin</a>.' ),
+				esc_url( admin_url( 'theme-install.php' ) )
+			),
+			'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 ),
-				// @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(
-				/* translators: %s: URL to Add Themes admin screen. */
-				__( 'You won&#8217;t be able to install new themes from here yet since your install requires SFTP credentials. For now, please <a href="%s">add themes in the admin</a>.' ),
-				esc_url( admin_url( 'theme-install.php' ) )
-			),
-			'publishSettings'         => __( 'Publish Settings' ),
-			'invalidDate'             => __( 'Invalid date.' ),
-			'invalidValue'            => __( 'Invalid value.' ),
 		)
 	);
 	$scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
