Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide correct WPCOM language code as locale when requesting CSV exports #10197

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

Provide correct language code when requesting CSV exports in a region-specific language
2 changes: 1 addition & 1 deletion client/data/deposits/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const formatQueryFilters = ( query ) => ( {
],
status_is: query.statusIs,
status_is_not: query.statusIsNot,
locale: query.userLocale,
locale: query.locale,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as a generic query param, since this could be site, user or manually-selected locale in the future. These endpoints do not mandate this to be the user locale.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Side note not relevant to this PR)

Is this formatQueryFilters operating as an "allow list"?

It looks like the outcome of this method is to:

  • Convert "pascalCase" keys to underscore snake_case vibe (server uses snake_case convention).
  • Format (render to consistent text) some query data e.g. dates.
  • And in the case of match and locale, allow them through unchanged.

Low priority questions…

  • Is the "allow list" functionality intended, would it be simpler to trust the client (calling code) and only format what needs formatting, allow any keys through?
  • Could we use a generic snake_case conversion instead of doing it manually, key by key?

} );

export function getDepositsCSV( query ) {
Expand Down
2 changes: 1 addition & 1 deletion client/data/disputes/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const formatQueryFilters = ( query ) => ( {
: query.search,
status_is: query.statusIs,
status_is_not: query.statusIsNot,
locale: query.userLocale,
locale: query.locale,
} );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment to above: are these intentionally allow-lists or could we simplify by using generic processing for query args.


export function getDisputesCSV( query ) {
Expand Down
2 changes: 1 addition & 1 deletion client/data/transactions/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
} );

/**
Expand Down
4 changes: 2 additions & 2 deletions client/deposits/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deceptive!

  • Is wcSettings.locale.userLocale a ~2-letter code?
  • wcpaySettings.userLocale.code is a struct with other stuff in it (code: english_name: ; native_name: ;) - do we need it in our settings?

Using the store settings seems cleaner to me 🚀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deceptive!

(i.e. took me a while to see what was changing)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG nice, you have removed wcpaySettings.userLocale 🫡


const {
date_before: dateBefore,
Expand Down Expand Up @@ -257,7 +257,7 @@ export const DepositsList = (): JSX.Element => {
} >( {
path: getDepositsCSV( {
userEmail,
userLocale,
locale,
dateAfter,
dateBefore,
dateBetween,
Expand Down
8 changes: 1 addition & 7 deletions client/deposits/list/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ declare const global: {
country: string;
};
dateFormat: string;
userLocale: {
code: string;
};
};
};

Expand Down Expand Up @@ -160,9 +157,6 @@ describe( 'Deposits list', () => {
},
},
dateFormat: 'M j Y',
userLocale: {
code: 'en',
},
};
} );

Expand Down Expand Up @@ -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',
} );
} );
} );
Expand Down
4 changes: 2 additions & 2 deletions client/disputes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -392,7 +392,7 @@ export const DisputesList = (): JSX.Element => {
} >( {
path: getDisputesCSV( {
userEmail,
userLocale,
locale,
dateAfter,
dateBefore,
dateBetween,
Expand Down
8 changes: 1 addition & 7 deletions client/disputes/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ declare const global: {
};
dateFormat?: string;
timeFormat?: string;
userLocale: {
code: string;
};
};
};

Expand Down Expand Up @@ -208,9 +205,6 @@ describe( 'Disputes list', () => {
},
dateFormat: 'Y-m-d',
timeFormat: 'g:iA',
userLocale: {
code: 'en',
},
};
} );

Expand Down Expand Up @@ -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',
} );
} );
} );
Expand Down
22 changes: 0 additions & 22 deletions client/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫡 oh captain, my captain

remove that redundant code 🧹

trackingInfo?: {
hosting_provider: string;
};
Expand Down
4 changes: 2 additions & 2 deletions client/transactions/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,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,
Expand Down Expand Up @@ -645,7 +645,6 @@ export const TransactionsList = (
await apiFetch( {
path: getTransactionsCSV( {
userEmail,
userLocale,
dateAfter,
dateBefore,
dateBetween,
Expand All @@ -666,6 +665,7 @@ export const TransactionsList = (
riskLevelIs,
riskLevelIsNot,
depositId,
locale,
} ),
method: 'POST',
} );
Expand Down
8 changes: 1 addition & 7 deletions client/transactions/list/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ declare const global: {
precision: number;
};
};
userLocale: {
code: string;
};
};
};

Expand Down Expand Up @@ -258,9 +255,6 @@ describe( 'Transactions list', () => {
precision: 2,
},
},
userLocale: {
code: 'en',
},
};
window.wcpaySettings.dateFormat = 'M j, Y';
window.wcpaySettings.timeFormat = 'g:iA';
Expand Down Expand Up @@ -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`,
} );
} );
} );
Expand Down
1 change: 0 additions & 1 deletion includes/admin/class-wc-payments-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
10 changes: 6 additions & 4 deletions includes/admin/class-wc-rest-payments-deposits-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ] );
}

/**
Expand Down
10 changes: 6 additions & 4 deletions includes/admin/class-wc-rest-payments-disputes-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice and simple to do this inline. Long term I wonder if we might want middleware-style generic converters for various standard args.

That is probably overengineering, but at the same time, I worry that our request wrapper handling in client is too manual, since it's almost a 1:1 mapping to the service API, tweaking a few things here and there. One way to document that "magic" is to implement as middleware pattern. For example, WooPayments JavaScript front end calls store PHP/REST API, store PHP generically adds authentication for WooPayments Service (WPCOM) API.


return $this->forward_request( 'get_disputes_export', [ $filters, $user_email, $locale ] );
return $this->forward_request( 'get_disputes_export', [ $filters, $user_email, $wpcom_locale ] );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ] );
}

/**
Expand Down
83 changes: 20 additions & 63 deletions includes/class-wc-payments-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_language_data is no longer used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. And to confirm, it was only used to generate/render our JS settings data, which was not needed?

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💣

* @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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick on naming, could be clearer IMO.

  • convert – is map clearer? I.e. we are mapping from one system to another.
  • server – I will always rail against the word server, it's too generic. In this case we could use wpcom (the platform our service runs on), or woopayments_service.
  • Maybe map_to_wpcom_locale_code? I think code helps too, clarify that the input and output are the ~2 letter code things; locale could be represented a few ways.

Also suggest refreshing the doc comment (it's pretty clear as is), especially if you tweak the name.

$wp_locale_lowercase = strtolower( $wp_locale );
$region_specific_wpcom_language_codes = [ 'pt_br', 'pt-br', 'zh_tw', 'zh-tw', 'zh_cn', 'zh-cn' ];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing the manual mapping of locales only to those that are region-specific.

This approach is borrowed from Jetpack's get_iso_639_locale()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is borrowed from Jetpack's get_iso_639_locale()

Recommend adding a comment "maintenance hint" about this, i.e. guide future team when WPCOM adds a new language (or removes one).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in the docblock.

$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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a mix of early-return and "return $language_code" style here. I recommend rewriting as early-return while here, then each case will be a single if, no nesting.

Hmm .. if this is verbatim from Jetpack code … maybe should leave as is? If so make that clear in the comments.

}

// Return 'en_US' to match the default site language.
return 'en_US';
return $language_code;
}

/**
Expand Down
7 changes: 2 additions & 5 deletions tests/js/jest-test-file-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ global.wcSettings = {
failed: 'Failed',
paid: 'Paid',
},
l10n: {
locale: {
siteLocale: 'en_US',
userLocale: 'en_US',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating this to the accurately match what is available in the wcSettings global JS variable.

weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
Expand All @@ -102,10 +103,6 @@ global.wpApiSettings = {
};

global.wcpaySettings = {
locale: {
code: 'es_ES',
native_name: 'Spanish',
},
accountLoans: {
loans: [ 'flxln_123456|active' ],
},
Expand Down
Loading
Loading