diff --git a/changelog/fix-10149-csv-export-dash-separated-locales-not-working b/changelog/fix-10149-csv-export-dash-separated-locales-not-working new file mode 100644 index 00000000000..970c629a5a5 --- /dev/null +++ b/changelog/fix-10149-csv-export-dash-separated-locales-not-working @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Provide correct language code when requesting CSV exports in a region-specific language diff --git a/client/data/deposits/resolvers.js b/client/data/deposits/resolvers.js index dd815282329..a4a0ad14e26 100644 --- a/client/data/deposits/resolvers.js +++ b/client/data/deposits/resolvers.js @@ -84,7 +84,7 @@ const formatQueryFilters = ( query ) => ( { ], status_is: query.statusIs, status_is_not: query.statusIsNot, - locale: query.userLocale, + locale: query.locale, } ); export function getDepositsCSV( query ) { diff --git a/client/data/disputes/resolvers.js b/client/data/disputes/resolvers.js index ccc72c001f3..ce748a46562 100644 --- a/client/data/disputes/resolvers.js +++ b/client/data/disputes/resolvers.js @@ -38,7 +38,7 @@ const formatQueryFilters = ( query ) => ( { : query.search, status_is: query.statusIs, status_is_not: query.statusIsNot, - locale: query.userLocale, + locale: query.locale, } ); export function getDisputesCSV( query ) { diff --git a/client/data/transactions/resolvers.js b/client/data/transactions/resolvers.js index 54d96fb7127..37b1c94fb0d 100644 --- a/client/data/transactions/resolvers.js +++ b/client/data/transactions/resolvers.js @@ -58,7 +58,7 @@ export const formatQueryFilters = ( query ) => ( { source_is_not: query.sourceIsNot, search: query.search, user_timezone: getUserTimeZone(), - locale: query.userLocale, + locale: query.locale, } ); /** diff --git a/client/deposits/list/index.tsx b/client/deposits/list/index.tsx index 22a1532dcc4..0420c4bcfd8 100644 --- a/client/deposits/list/index.tsx +++ b/client/deposits/list/index.tsx @@ -214,7 +214,7 @@ export const DepositsList = (): JSX.Element => { const endpointExport = async () => { const userEmail = wcpaySettings.currentUserEmail; - const userLocale = wcpaySettings.userLocale.code; + const locale = wcSettings.locale.userLocale; const { date_before: dateBefore, @@ -257,7 +257,7 @@ export const DepositsList = (): JSX.Element => { } >( { path: getDepositsCSV( { userEmail, - userLocale, + locale, dateAfter, dateBefore, dateBetween, diff --git a/client/deposits/list/test/index.tsx b/client/deposits/list/test/index.tsx index 9698a50f899..5226853c564 100644 --- a/client/deposits/list/test/index.tsx +++ b/client/deposits/list/test/index.tsx @@ -90,9 +90,6 @@ declare const global: { country: string; }; dateFormat: string; - userLocale: { - code: string; - }; }; }; @@ -160,9 +157,6 @@ describe( 'Deposits list', () => { }, }, dateFormat: 'M j Y', - userLocale: { - code: 'en', - }, }; } ); @@ -375,7 +369,7 @@ describe( 'Deposits list', () => { expect( mockApiFetch ).toHaveBeenCalledWith( { method: 'POST', path: - '/wc/v3/payments/deposits/download?user_email=mock%40example.com&locale=en', + '/wc/v3/payments/deposits/download?user_email=mock%40example.com&locale=en_US', } ); } ); } ); diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx index 73837d8e865..1253a84efe3 100644 --- a/client/disputes/index.tsx +++ b/client/disputes/index.tsx @@ -351,7 +351,7 @@ export const DisputesList = (): JSX.Element => { const { page, path, ...params } = getQuery(); const userEmail = wcpaySettings.currentUserEmail; - const userLocale = wcpaySettings.userLocale.code; + const locale = wcSettings.locale.userLocale; const { date_before: dateBefore, date_after: dateAfter, @@ -392,7 +392,7 @@ export const DisputesList = (): JSX.Element => { } >( { path: getDisputesCSV( { userEmail, - userLocale, + locale, dateAfter, dateBefore, dateBetween, diff --git a/client/disputes/test/index.tsx b/client/disputes/test/index.tsx index 2d20360426c..a0bc3f7a4b7 100644 --- a/client/disputes/test/index.tsx +++ b/client/disputes/test/index.tsx @@ -104,9 +104,6 @@ declare const global: { }; dateFormat?: string; timeFormat?: string; - userLocale: { - code: string; - }; }; }; @@ -208,9 +205,6 @@ describe( 'Disputes list', () => { }, dateFormat: 'Y-m-d', timeFormat: 'g:iA', - userLocale: { - code: 'en', - }, }; } ); @@ -335,7 +329,7 @@ describe( 'Disputes list', () => { expect( mockApiFetch ).toHaveBeenCalledWith( { method: 'POST', path: - '/wc/v3/payments/disputes/download?user_email=mock%40example.com&locale=en', + '/wc/v3/payments/disputes/download?user_email=mock%40example.com&locale=en_US', } ); } ); } ); diff --git a/client/globals.d.ts b/client/globals.d.ts index 0b61d866700..c721206e1bd 100644 --- a/client/globals.d.ts +++ b/client/globals.d.ts @@ -123,28 +123,6 @@ declare global { isNextDepositNoticeDismissed: boolean; isInstantDepositNoticeDismissed: boolean; isConnectionSuccessModalDismissed: boolean; - userLocale: { - /** - * The locale of the current user profile, represented as a locale code supported by transact-platform-server. - * - * @example 'es' // Spanish - * - * @see WC_Payments_Utils::convert_locale_to_language_code - */ - code: string; - /** - * The English name of the locale. - * - * @example 'Spanish' - */ - english_name: string; - /** - * The native name of the locale. - * - * @example 'EspaƱol' - */ - native_name: string; - }; trackingInfo?: { hosting_provider: string; }; diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index 9d0be7a2196..63a69ff5464 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -591,8 +591,8 @@ export const TransactionsList = ( // eslint-disable-next-line @typescript-eslint/no-unused-vars const { page, path, ...params } = getQuery(); const userEmail = wcpaySettings.currentUserEmail; + const locale = wcSettings.locale.userLocale; - const userLocale = wcpaySettings.userLocale.code; const { date_after: dateAfter, date_before: dateBefore, @@ -652,7 +652,6 @@ export const TransactionsList = ( await apiFetch( { path: getTransactionsCSV( { userEmail, - userLocale, dateAfter, dateBefore, dateBetween, @@ -673,6 +672,7 @@ export const TransactionsList = ( riskLevelIs, riskLevelIsNot, depositId, + locale, } ), method: 'POST', } ); diff --git a/client/transactions/list/test/index.tsx b/client/transactions/list/test/index.tsx index 5d4efbf827e..45f318f053a 100644 --- a/client/transactions/list/test/index.tsx +++ b/client/transactions/list/test/index.tsx @@ -111,9 +111,6 @@ declare const global: { precision: number; }; }; - userLocale: { - code: string; - }; }; }; @@ -258,9 +255,6 @@ describe( 'Transactions list', () => { precision: 2, }, }, - userLocale: { - code: 'en', - }, }; window.wcpaySettings.dateFormat = 'M j, Y'; window.wcpaySettings.timeFormat = 'g:iA'; @@ -670,7 +664,7 @@ describe( 'Transactions list', () => { method: 'POST', path: `/wc/v3/payments/transactions/download?user_email=mock%40example.com&deposit_id=po_mock&user_timezone=${ encodeURIComponent( getUserTimeZone() - ) }&locale=en`, + ) }&locale=en_US`, } ); } ); } ); diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 5a1a6941682..9badfd47a8a 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -974,7 +974,6 @@ private function get_js_settings(): array { 'isInstantDepositNoticeDismissed' => get_option( 'wcpay_instant_deposit_notice_dismissed', false ), 'dismissedDuplicateNotices' => get_option( 'wcpay_duplicate_payment_method_notices_dismissed', [] ), 'isConnectionSuccessModalDismissed' => get_option( 'wcpay_connection_success_modal_dismissed', false ), - 'userLocale' => WC_Payments_Utils::get_language_data( get_user_locale() ), // Language code found in current user's profile. This is different from the site's locale. 'isOverviewSurveySubmitted' => get_option( 'wcpay_survey_payment_overview_submitted', false ), 'trackingInfo' => $this->account->get_tracking_info(), 'lifetimeTPV' => $this->account->get_lifetime_total_payment_volume(), diff --git a/includes/admin/class-wc-rest-payments-deposits-controller.php b/includes/admin/class-wc-rest-payments-deposits-controller.php index 1b5c808a314..fb127a7eb04 100644 --- a/includes/admin/class-wc-rest-payments-deposits-controller.php +++ b/includes/admin/class-wc-rest-payments-deposits-controller.php @@ -132,11 +132,13 @@ public function get_deposit( $request ) { * @param WP_REST_Request $request Full data about the request. */ public function get_deposits_export( $request ) { - $user_email = $request->get_param( 'user_email' ); - $locale = $request->get_param( 'locale' ); - $filters = $this->get_deposits_filters( $request ); + $user_email = $request->get_param( 'user_email' ); + $wpcom_locale = WC_Payments_Utils::convert_to_server_locale( + $request->get_param( 'locale' ) + ); + $filters = $this->get_deposits_filters( $request ); - return $this->forward_request( 'get_deposits_export', [ $filters, $user_email, $locale ] ); + return $this->forward_request( 'get_deposits_export', [ $filters, $user_email, $wpcom_locale ] ); } /** diff --git a/includes/admin/class-wc-rest-payments-disputes-controller.php b/includes/admin/class-wc-rest-payments-disputes-controller.php index 64e1e478a21..565e93c2ece 100644 --- a/includes/admin/class-wc-rest-payments-disputes-controller.php +++ b/includes/admin/class-wc-rest-payments-disputes-controller.php @@ -147,11 +147,13 @@ public function close_dispute( $request ) { * @param WP_REST_Request $request Full data about the request. */ public function get_disputes_export( $request ) { - $user_email = $request->get_param( 'user_email' ); - $locale = $request->get_param( 'locale' ); - $filters = $this->get_disputes_filters( $request ); + $user_email = $request->get_param( 'user_email' ); + $wpcom_locale = WC_Payments_Utils::convert_to_server_locale( + $request->get_param( 'locale' ) + ); + $filters = $this->get_disputes_filters( $request ); - return $this->forward_request( 'get_disputes_export', [ $filters, $user_email, $locale ] ); + return $this->forward_request( 'get_disputes_export', [ $filters, $user_email, $wpcom_locale ] ); } /** diff --git a/includes/admin/class-wc-rest-payments-transactions-controller.php b/includes/admin/class-wc-rest-payments-transactions-controller.php index 9e7c10b76a4..a5cb55bac70 100644 --- a/includes/admin/class-wc-rest-payments-transactions-controller.php +++ b/includes/admin/class-wc-rest-payments-transactions-controller.php @@ -162,12 +162,14 @@ public function get_fraud_outcome_transactions_export( $request ) { * @param WP_REST_Request $request Full data about the request. */ public function get_transactions_export( $request ) { - $user_email = $request->get_param( 'user_email' ); - $deposit_id = $request->get_param( 'deposit_id' ); - $locale = $request->get_param( 'locale' ); - $filters = $this->get_transactions_filters( $request ); + $user_email = $request->get_param( 'user_email' ); + $deposit_id = $request->get_param( 'deposit_id' ); + $wpcom_locale = WC_Payments_Utils::convert_to_server_locale( + $request->get_param( 'locale' ) + ); + $filters = $this->get_transactions_filters( $request ); - return $this->forward_request( 'get_transactions_export', [ $filters, $user_email, $deposit_id, $locale ] ); + return $this->forward_request( 'get_transactions_export', [ $filters, $user_email, $deposit_id, $wpcom_locale ] ); } /** diff --git a/includes/class-wc-payments-utils.php b/includes/class-wc-payments-utils.php index 24608d2c898..16ea71292cd 100644 --- a/includes/class-wc-payments-utils.php +++ b/includes/class-wc-payments-utils.php @@ -1251,74 +1251,31 @@ public static function enqueue_style( $handle, $path = '', $deps = [], $version } /** - * Returns language data: english name and native name + * Converts a WP locale to a wpcom-compatible language code. * - * @param string $language Language code. + * @see Automattic\Jetpack\Jetpack_Mu_Wpcom\Common::get_iso_639_locale() for similar logic. + * @see https://translate.wordpress.com/projects/wpcom/ for the current state of wpcom translations. * - * @return array - */ - public static function get_language_data( $language ) { - require_once ABSPATH . 'wp-admin/includes/translation-install.php'; - - $translations = wp_get_available_translations(); - - if ( isset( $translations[ $language ] ) ) { - return [ - 'code' => self::convert_to_server_locale( $language ), - 'english_name' => $translations[ $language ]['english_name'] ?? $language, - 'native_name' => $translations[ $language ]['native_name'] ?? $language, - ]; - } - - return [ - 'code' => 'en_US', - 'english_name' => 'English (United States)', - 'native_name' => 'English (United States)', - ]; - } - - /** - * Converts a locale to the server supported languages. - * - * @param string $locale The locale to convert. - * - * @return string Closest locale supported ('en' if NONE) + * @param string $wp_locale a WordPress locale code to be converted e.g. "en_US", "pt_BR". + * @return string language code compatible with wpcom e.g. "en", "pt-br". */ - public static function convert_to_server_locale( string $locale ): string { - $supported = [ - 'ar', // Arabic. - 'de', // German (Germany). - 'es', // Spanish (Spain). - 'fr', // French (France). - 'he', // Hebrew (Israel). - 'id', // Indonesian (Indonesia). - 'it', // Italian (Italy). - 'ja', // Japanese. - 'ko', // Korean. - 'nl', // Dutch (Netherlands). - 'pt-br', // Portuguese (Brazil). - 'ru', // Russian (Russia). - 'sv', // Swedish (Sweden). - 'tr', // Turkish (Turkey). - 'zh-cn', // Simplified, Singapore). - 'zh-tw', // Chinese Traditional (Taiwan). - ]; - - // Replace '-' with '_' (used in WordPress). - $locale = str_replace( '_', '-', $locale ); - - if ( in_array( $locale, $supported, true ) ) { - return $locale; - } - - // Remove the country code and try with that. - $base_locale = substr( $locale, 0, 2 ); - if ( in_array( $base_locale, $supported, true ) ) { - return $base_locale; + public static function convert_to_server_locale( string $wp_locale ): string { + $wp_locale_lowercase = strtolower( $wp_locale ); + $region_specific_wpcom_language_codes = [ 'pt_br', 'pt-br', 'zh_tw', 'zh-tw', 'zh_cn', 'zh-cn' ]; + $is_region_specific_wpcom_language_code = in_array( $wp_locale_lowercase, $region_specific_wpcom_language_codes, true ); + + $language_code = $is_region_specific_wpcom_language_code ? + // If it is a region-specific language code, we replace the underscore with a dash, e.g. 'pt_br' => 'pt-br'. + str_replace( '_', '-', $wp_locale_lowercase ) : + // Otherwise, we remove the country code and return only the language code, e.g. 'nl_NL' => 'nl'. + preg_replace( '/([-_].*)$/i', '', $wp_locale_lowercase ); + + if ( empty( $language_code ) ) { + // If the language code is empty, we return 'en' as a fallback. + return 'en'; } - // Return 'en_US' to match the default site language. - return 'en_US'; + return $language_code; } /** diff --git a/tests/js/jest-test-file-setup.js b/tests/js/jest-test-file-setup.js index 09400abae27..59d98322955 100644 --- a/tests/js/jest-test-file-setup.js +++ b/tests/js/jest-test-file-setup.js @@ -75,7 +75,8 @@ global.wcSettings = { failed: 'Failed', paid: 'Paid', }, - l10n: { + locale: { + siteLocale: 'en_US', userLocale: 'en_US', weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], }, @@ -102,10 +103,6 @@ global.wpApiSettings = { }; global.wcpaySettings = { - locale: { - code: 'es_ES', - native_name: 'Spanish', - }, accountLoans: { loans: [ 'flxln_123456|active' ], }, diff --git a/tests/unit/test-class-wc-payments-utils.php b/tests/unit/test-class-wc-payments-utils.php index 61aa747fc00..25befd5eaf3 100644 --- a/tests/unit/test-class-wc-payments-utils.php +++ b/tests/unit/test-class-wc-payments-utils.php @@ -891,6 +891,37 @@ public function test_convert_to_stripe_locale() { $this->assertEquals( 'es', $result ); } + /** + * @dataProvider provider_convert_to_server_locale + */ + public function test_convert_to_server_locale( string $input_locale, string $expected_locale ) { + $result = WC_Payments_Utils::convert_to_server_locale( $input_locale ); + $this->assertEquals( $expected_locale, $result ); + } + + public function provider_convert_to_server_locale(): array { + // Label => [ WordPress locale, transact-platform-server locale ]. + return [ + 'Supported locale (Arabic)' => [ 'ar_AR', 'ar' ], + 'Supported locale (German)' => [ 'de_DE', 'de' ], + 'Supported locale (Spanish)' => [ 'es_ES', 'es' ], + 'Supported locale (French)' => [ 'fr_FR', 'fr' ], + 'Supported locale (Hebrew)' => [ 'he_IL', 'he' ], + 'Supported locale (Indonesian)' => [ 'id_ID', 'id' ], + 'Supported locale (Italian)' => [ 'it_IT', 'it' ], + 'Supported locale (Japanese)' => [ 'ja_JP', 'ja' ], + 'Supported locale (Korean)' => [ 'ko_KR', 'ko' ], + 'Supported locale (Dutch)' => [ 'nl_NL', 'nl' ], + 'Supported locale (Portuguese (Brazil))' => [ 'pt_BR', 'pt-br' ], + 'Supported locale (Russian)' => [ 'ru_RU', 'ru' ], + 'Supported locale (Swedish)' => [ 'sv_SE', 'sv' ], + 'Supported locale (Turkish)' => [ 'tr_TR', 'tr' ], + 'Supported locale (Chinese (Simplified))' => [ 'zh_CN', 'zh-cn' ], + 'Supported locale (Chinese (Traditional))' => [ 'zh_TW', 'zh-tw' ], + 'Empty locale fallback to en' => [ '', 'en' ], + ]; + } + public function not_payment_settings_page_conditions_provider(): array { return [ 'is_admin() is false' => [ false, 'woocommerce_payments_foo', 'checkout' ],