Merge pull request woocommerce/woocommerce-admin#457 from woocommerce/fix/advanced-filters-i18n

i18n: Advanced Filters strings
This commit is contained in:
Paul Sealock 2018-10-16 10:54:45 +13:00 committed by GitHub
commit 4bc8c82820
9 changed files with 316 additions and 242 deletions

View File

@ -2,7 +2,7 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, _x } from '@wordpress/i18n';
/**
* Internal dependencies
@ -30,87 +30,123 @@ export const filters = [
{ label: __( 'Advanced Filters', 'wc-admin' ), value: 'advanced' },
];
/*eslint-disable max-len*/
export const advancedFilterConfig = {
status: {
labels: {
add: __( 'Order Status', 'wc-admin' ),
remove: __( 'Remove order status filter', 'wc-admin' ),
rule: __( 'Select an order status filter match', 'wc-admin' ),
title: __( 'Order Status', 'wc-admin' ),
},
rules: [
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
],
input: {
component: 'SelectControl',
options: Object.keys( orderStatuses ).map( key => ( {
value: key,
label: orderStatuses[ key ],
} ) ),
defaultOption: 'wc-cancelled',
},
},
product_id: {
labels: {
add: __( 'Products', 'wc-admin' ),
placeholder: __( 'Search products', 'wc-admin' ),
remove: __( 'Remove products filter', 'wc-admin' ),
rule: __( 'Select a product filter match', 'wc-admin' ),
title: __( 'Product', 'wc-admin' ),
},
rules: [
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
],
input: {
component: 'Search',
type: 'products',
getLabels: getRequestByIdString( NAMESPACE + 'products', product => ( {
id: product.id,
label: product.name,
} ) ),
},
},
code: {
labels: {
add: __( 'Coupon Codes', 'wc-admin' ),
placeholder: __( 'Search coupons', 'wc-admin' ),
remove: __( 'Remove coupon filter', 'wc-admin' ),
rule: __( 'Select a coupon filter match', 'wc-admin' ),
title: __( 'Coupon Code', 'wc-admin' ),
},
rules: [
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
],
input: {
component: 'Search',
type: 'coupons',
getLabels: getRequestByIdString( NAMESPACE + 'coupons', coupon => ( {
id: coupon.id,
label: coupon.code,
} ) ),
},
},
customer: {
labels: {
add: __( 'Customer Type', 'wc-admin' ),
remove: __( 'Remove customer filter', 'wc-admin' ),
rule: __( 'Select a customer filter match', 'wc-admin' ),
title: __( 'Customer is', 'wc-admin' ),
},
input: {
component: 'SelectControl',
options: [
{ value: 'new', label: __( 'New', 'wc-admin' ) },
{ value: 'returning', label: __( 'Returning', 'wc-admin' ) },
title: _x(
'Orders Match {{select /}} Filters',
'A sentence describing filters for Orders. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ',
'wc-admin'
),
filters: {
status: {
labels: {
add: __( 'Order Status', 'wc-admin' ),
remove: __( 'Remove order status filter', 'wc-admin' ),
rule: __( 'Select an order status filter match', 'wc-admin' ),
/* translators: A sentence describing an Order Status filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
title: __( 'Order Status {{rule /}} {{filter /}}', 'wc-admin' ),
filter: __( 'Select an order status', 'wc-admin' ),
},
rules: [
{
value: 'is',
/* translators: Sentence fragment, logical, "Is" refers to searching for orders matching a chosen order status. Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Is', 'order status', 'wc-admin' ),
},
{
value: 'is_not',
/* translators: Sentence fragment, logical, "Is Not" refers to searching for orders that don\'t match a chosen order status. Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Is Not', 'order status', 'wc-admin' ),
},
],
defaultOption: 'new',
input: {
component: 'SelectControl',
options: Object.keys( orderStatuses ).map( key => ( {
value: key,
label: orderStatuses[ key ],
} ) ),
},
},
product: {
labels: {
add: __( 'Products', 'wc-admin' ),
placeholder: __( 'Search products', 'wc-admin' ),
remove: __( 'Remove products filter', 'wc-admin' ),
rule: __( 'Select a product filter match', 'wc-admin' ),
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
title: __( 'Product {{rule /}} {{filter /}}', 'wc-admin' ),
filter: __( 'Select products', 'wc-admin' ),
},
rules: [
{
value: 'includes',
/* translators: Sentence fragment, logical, "Includes" refers to orders including a given product(s). Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Includes', 'products', 'wc-admin' ),
},
{
value: 'excludes',
/* translators: Sentence fragment, logical, "Excludes" refers to orders excluding a given product(s). Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Excludes', 'products', 'wc-admin' ),
},
],
input: {
component: 'Search',
type: 'products',
getLabels: getRequestByIdString( NAMESPACE + 'products', product => ( {
id: product.id,
label: product.name,
} ) ),
},
},
code: {
labels: {
add: __( 'Coupon Codes', 'wc-admin' ),
placeholder: __( 'Search coupons', 'wc-admin' ),
remove: __( 'Remove coupon filter', 'wc-admin' ),
rule: __( 'Select a coupon filter match', 'wc-admin' ),
/* translators: A sentence describing a Coupon filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
title: __( 'Coupon Code {{rule /}} {{filter /}}', 'wc-admin' ),
filter: __( 'Select coupon codes', 'wc-admin' ),
},
rules: [
{
value: 'includes',
/* translators: Sentence fragment, logical, "Includes" refers to orders including a given coupon code(s). Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Includes', 'coupon code', 'wc-admin' ),
},
{
value: 'excludes',
/* translators: Sentence fragment, logical, "Excludes" refers to orders excluding a given coupon code(s). Screenshot for context: https://cloudup.com/cSsUY9VeCVJ */
label: _x( 'Excludes', 'coupon code', 'wc-admin' ),
},
],
input: {
component: 'Search',
type: 'coupons',
getLabels: getRequestByIdString( NAMESPACE + 'coupons', coupon => ( {
id: coupon.id,
label: coupon.code,
} ) ),
},
},
customer: {
labels: {
add: __( 'Customer Type', 'wc-admin' ),
remove: __( 'Remove customer filter', 'wc-admin' ),
rule: __( 'Select a customer filter match', 'wc-admin' ),
/* translators: A sentence describing a Customer filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
title: __( 'Customer is {{filter /}}', 'wc-admin' ),
filter: __( 'Select a customer type', 'wc-admin' ),
},
input: {
component: 'SelectControl',
options: [
{ value: 'new', label: __( 'New', 'wc-admin' ) },
{ value: 'returning', label: __( 'Returning', 'wc-admin' ) },
],
defaultOption: 'new',
},
},
},
};
/*eslint-enable max-len*/

View File

@ -2,12 +2,13 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Component, Fragment, createRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Component, createRef } from '@wordpress/element';
import { SelectControl, Button, Dropdown, IconButton } from '@wordpress/components';
import { partial, findIndex, difference } from 'lodash';
import PropTypes from 'prop-types';
import Gridicon from 'gridicons';
import interpolateComponents from 'interpolate-components';
/**
* Internal dependencies
@ -37,7 +38,7 @@ class AdvancedFilters extends Component {
super( ...arguments );
this.state = {
match: query.match || 'all',
activeFilters: getActiveFiltersFromQuery( query, config ),
activeFilters: getActiveFiltersFromQuery( query, config.filters ),
};
this.filterListRef = createRef();
@ -75,30 +76,31 @@ class AdvancedFilters extends Component {
getTitle() {
const { match } = this.state;
const { filterTitle } = this.props;
return (
<Fragment>
<span>{ sprintf( __( '%s Match', 'wc-admin' ), filterTitle ) }</span>
<SelectControl
className="woocommerce-filters-advanced__title-select"
options={ matches }
value={ match }
onChange={ this.onMatchChange }
aria-label={ __( 'Match any or all filters', 'wc-admin' ) }
/>
<span>{ __( 'Filters', 'wc-admin' ) }</span>
</Fragment>
);
const { config } = this.props;
return interpolateComponents( {
mixedString: config.title,
components: {
select: (
<SelectControl
className="woocommerce-filters-advanced__title-select"
options={ matches }
value={ match }
onChange={ this.onMatchChange }
aria-label={ __( 'Choose to apply any or all filters', 'wc-admin' ) }
/>
),
},
} );
}
getAvailableFilterKeys() {
const { config } = this.props;
const activeFilterKeys = this.state.activeFilters.map( f => f.key );
return difference( Object.keys( config ), activeFilterKeys );
return difference( Object.keys( config.filters ), activeFilterKeys );
}
addFilter( key, onClose ) {
const filterConfig = this.props.config[ key ];
const filterConfig = this.props.config.filters[ key ];
const newFilter = { key };
if ( Array.isArray( filterConfig.rules ) && filterConfig.rules.length ) {
newFilter.rule = filterConfig.rules[ 0 ].value;
@ -131,46 +133,47 @@ class AdvancedFilters extends Component {
getUpdateHref( activeFilters, matchValue ) {
const { path, query, config } = this.props;
const updatedQuery = getQueryFromActiveFilters( activeFilters, query, config );
const updatedQuery = getQueryFromActiveFilters( activeFilters, query, config.filters );
const match = matchValue === 'all' ? undefined : matchValue;
return getNewPath( { ...updatedQuery, match }, path, query );
}
isEnglish() {
const { siteLocale } = wcSettings;
return /en-/.test( siteLocale );
}
render() {
const { config } = this.props;
const { activeFilters, match } = this.state;
const availableFilterKeys = this.getAvailableFilterKeys();
const updateHref = this.getUpdateHref( activeFilters, match );
const updateDisabled = window.location.hash && window.location.hash.substr( 1 ) === updateHref;
const isEnglish = this.isEnglish();
return (
<Card className="woocommerce-filters-advanced" title={ this.getTitle() }>
<ul className="woocommerce-filters-advanced__list" ref={ this.filterListRef }>
{ activeFilters.map( filter => {
const { key } = filter;
const { input, labels } = config[ key ];
const { input, labels } = config.filters[ key ];
return (
<li className="woocommerce-filters-advanced__list-item" key={ key }>
{ /*eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex*/ }
<fieldset tabIndex="0">
{ /*eslint-enable-next-line jsx-a11y/no-noninteractive-tabindex*/ }
<legend className="screen-reader-text">{ labels.title }</legend>
<div className="woocommerce-filters-advanced__fieldset">
{ 'SelectControl' === input.component && (
<SelectFilter
filter={ filter }
config={ config[ key ] }
onFilterChange={ this.onFilterChange }
/>
) }
{ 'Search' === input.component && (
<SearchFilter
filter={ filter }
config={ config[ key ] }
onFilterChange={ this.onFilterChange }
/>
) }
</div>
</fieldset>
{ 'SelectControl' === input.component && (
<SelectFilter
filter={ filter }
config={ config.filters[ key ] }
onFilterChange={ this.onFilterChange }
isEnglish={ isEnglish }
/>
) }
{ 'Search' === input.component && (
<SearchFilter
filter={ filter }
config={ config.filters[ key ] }
onFilterChange={ this.onFilterChange }
isEnglish={ isEnglish }
/>
) }
<IconButton
className="woocommerce-filters-advanced__remove"
label={ labels.remove }
@ -201,7 +204,7 @@ class AdvancedFilters extends Component {
{ availableFilterKeys.map( key => (
<li key={ key }>
<Button onClick={ partial( this.addFilter, key, onClose ) }>
{ config[ key ].labels.add }
{ config.filters[ key ].labels.add }
</Button>
</li>
) ) }
@ -239,25 +242,25 @@ AdvancedFilters.propTypes = {
/**
* The configuration object required to render filters.
*/
config: PropTypes.objectOf(
PropTypes.shape( {
labels: PropTypes.shape( {
add: PropTypes.string,
placeholder: PropTypes.string,
remove: PropTypes.string,
title: PropTypes.string,
} ),
rules: PropTypes.arrayOf( PropTypes.object ),
input: PropTypes.object,
} )
).isRequired,
config: PropTypes.shape( {
title: PropTypes.string,
filters: PropTypes.objectOf(
PropTypes.shape( {
labels: PropTypes.shape( {
add: PropTypes.string,
remove: PropTypes.string,
rule: PropTypes.string,
title: PropTypes.string,
filter: PropTypes.string,
} ),
rules: PropTypes.arrayOf( PropTypes.object ),
input: PropTypes.object,
} )
),
} ).isRequired,
/**
* Name of this filter, used in translations.
*/
filterTitle: PropTypes.string.isRequired,
/**
* The `path` parameter supplied by React-Router.
*/
path: PropTypes.string.isRequired,
/**
* The query string represented in object form.

View File

@ -2,11 +2,12 @@
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import { Component } from '@wordpress/element';
import { SelectControl } from '@wordpress/components';
import { partial } from 'lodash';
import { find, partial } from 'lodash';
import PropTypes from 'prop-types';
import { withInstanceId } from '@wordpress/compose';
import interpolateComponents from 'interpolate-components';
import classnames from 'classnames';
/**
* Internal dependencies
@ -41,40 +42,66 @@ class SearchFilter extends Component {
onFilterChange( filter.key, 'value', idList );
}
getLegend( filter, config ) {
const { selected } = this.state;
const rule = find( config.rules, { value: filter.rule } ) || {};
const filterStr = selected.map( item => item.label ).join( ', ' );
return interpolateComponents( {
mixedString: config.labels.title,
components: {
filter: <span>{ filterStr }</span>,
rule: <span>{ rule.label }</span>,
},
} );
}
render() {
const { config, filter, instanceId, onFilterChange } = this.props;
const { config, filter, onFilterChange, isEnglish } = this.props;
const { selected } = this.state;
const { key, rule } = filter;
const { input, labels, rules } = config;
return (
<Fragment>
<div
id={ `${ key }-${ instanceId }` }
className="woocommerce-filters-advanced__fieldset-legend"
>
{ labels.title }
</div>
{ rule && (
const children = interpolateComponents( {
mixedString: labels.title,
components: {
rule: (
<SelectControl
className="woocommerce-filters-advanced__list-specifier"
className="woocommerce-filters-advanced__rule"
options={ rules }
value={ rule }
onChange={ partial( onFilterChange, key, 'rule' ) }
aria-label={ labels.rule }
/>
) }
<div className="woocommerce-filters-advanced__list-selector">
),
filter: (
<Search
className="woocommerce-filters-advanced__input"
onChange={ this.onSearchChange }
type={ input.type }
placeholder={ labels.placeholder }
selected={ selected }
ariaLabelledby={ `${ key }-${ instanceId }` }
inlineTags
aria-label={ labels.filter }
/>
),
},
} );
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/
return (
<fieldset tabIndex="0">
<legend className="screen-reader-text">
{ this.getLegend( filter, config, selected ) }
</legend>
<div
className={ classnames( 'woocommerce-filters-advanced__fieldset', {
'is-english': isEnglish,
} ) }
>
{ children }
</div>
</Fragment>
</fieldset>
);
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/
}
}
@ -105,4 +132,4 @@ SearchFilter.propTypes = {
onFilterChange: PropTypes.func.isRequired,
};
export default withInstanceId( SearchFilter );
export default SearchFilter;

View File

@ -2,11 +2,12 @@
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import { Component } from '@wordpress/element';
import { SelectControl, Spinner } from '@wordpress/components';
import { partial } from 'lodash';
import { find, partial } from 'lodash';
import PropTypes from 'prop-types';
import { withInstanceId } from '@wordpress/compose';
import interpolateComponents from 'interpolate-components';
import classnames from 'classnames';
/**
* Internal dependencies
@ -40,42 +41,62 @@ class SelectFilter extends Component {
return options;
}
getLegend( filter, config ) {
const rule = find( config.rules, { value: filter.rule } ) || {};
const value = find( config.input.options, { value: filter.value } ) || {};
return interpolateComponents( {
mixedString: config.labels.title,
components: {
filter: <span>{ value.label }</span>,
rule: <span>{ rule.label }</span>,
},
} );
}
render() {
const { config, filter, instanceId, onFilterChange } = this.props;
const { config, filter, onFilterChange, isEnglish } = this.props;
const { options } = this.state;
const { key, rule, value } = filter;
const { labels, rules } = config;
return (
<Fragment>
<div
id={ `${ key }-${ instanceId }` }
className="woocommerce-filters-advanced__fieldset-legend"
>
{ labels.title }
</div>
{ rule && (
const children = interpolateComponents( {
mixedString: labels.title,
components: {
rule: (
<SelectControl
className="woocommerce-filters-advanced__list-specifier"
className="woocommerce-filters-advanced__rule"
options={ rules }
value={ rule }
onChange={ partial( onFilterChange, key, 'rule' ) }
aria-label={ labels.rule }
/>
) }
<div className="woocommerce-filters-advanced__list-selector">
{ ! options && <Spinner /> }
{ options && (
<SelectControl
className="woocommerce-filters-advanced__list-select"
options={ options }
value={ value }
onChange={ partial( onFilterChange, filter.key, 'value' ) }
aria-labelledby={ `${ key }-${ instanceId }` }
/>
) }
),
filter: options ? (
<SelectControl
className="woocommerce-filters-advanced__input"
options={ options }
value={ value }
onChange={ partial( onFilterChange, filter.key, 'value' ) }
aria-label={ labels.filter }
/>
) : (
<Spinner />
),
},
} );
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/
return (
<fieldset tabIndex="0">
<legend className="screen-reader-text">{ this.getLegend( filter, config ) }</legend>
<div
className={ classnames( 'woocommerce-filters-advanced__fieldset', {
'is-english': isEnglish,
} ) }
>
{ children }
</div>
</Fragment>
</fieldset>
);
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/
}
}
@ -87,6 +108,7 @@ SelectFilter.propTypes = {
labels: PropTypes.shape( {
rule: PropTypes.string,
title: PropTypes.string,
filter: PropTypes.string,
} ),
rules: PropTypes.arrayOf( PropTypes.object ),
input: PropTypes.object,
@ -105,4 +127,4 @@ SelectFilter.propTypes = {
onFilterChange: PropTypes.func.isRequired,
};
export default withInstanceId( SelectFilter );
export default SelectFilter;

View File

@ -41,17 +41,13 @@
border-bottom: 1px solid $core-grey-light-700;
fieldset {
padding: $gap-smaller $gap;
padding: $gap-smaller $gap-smaller $gap-smaller $gap;
}
&:hover {
background-color: $core-grey-light-200;
}
.components-base-control {
margin: 0;
}
.woocommerce-filters-advanced__remove {
width: 40px;
height: 38px;
@ -85,22 +81,41 @@
}
.woocommerce-filters-advanced__fieldset {
display: grid;
grid-template-columns: 100px 150px auto;
& > div {
padding: 0 $gap-smallest;
@include breakpoint( '<782px' ) {
display: block;
margin: 0;
width: 100%;
padding: $gap-smallest 0;
}
}
display: flex;
align-items: center;
white-space: nowrap;
@include breakpoint( '<782px' ) {
display: flex;
flex-direction: column;
display: block;
}
&.is-english {
display: grid;
grid-template-columns: 100px 150px auto;
@include breakpoint( '<782px' ) {
display: block;
}
}
}
.woocommerce-filters-advanced__fieldset-legend {
align-self: center;
.woocommerce-filters-advanced__rule {
width: 150px;
}
@include breakpoint( '<782px' ) {
align-self: initial;
padding: $gap-smallest 0;
}
.woocommerce-filters-advanced__input {
width: 100%;
}
.woocommerce-filters-advanced__add-filter-dropdown {
@ -156,35 +171,3 @@
}
}
}
.woocommerce-filters-advanced__list-selector {
@include breakpoint( '<782px' ) {
padding: $gap-smallest 0;
}
.components-spinner {
margin: auto;
display: block;
float: none;
top: 50%;
transform: translateY(-50%);
@include breakpoint( '<782px' ) {
transform: none;
}
}
}
.woocommerce-filters-advanced__list-specifier {
@include breakpoint( '<782px' ) {
padding: $gap-smallest 0;
}
& + .woocommerce-filters-advanced__list-selector {
padding: 0 0 0 $gap-smaller;
@include breakpoint( '<782px' ) {
padding: $gap-smallest 0;
}
}
}

View File

@ -45,12 +45,7 @@ class ReportFilters extends Component {
if ( 'advanced' === query.filter ) {
return (
<div className="woocommerce-filters__advanced-filters">
<AdvancedFilters
config={ advancedConfig }
filterTitle={ __( 'Orders', 'wc-admin' ) }
path={ path }
query={ query }
/>
<AdvancedFilters config={ advancedConfig } path={ path } query={ query } />
</div>
);
}

View File

@ -17,6 +17,10 @@
}
}
}
.components-base-control__field {
margin-bottom: 0;
}
}
.woocommerce-filters-date,

View File

@ -113,14 +113,14 @@ class Search extends Component {
render() {
const autocompleter = this.getAutocompleter();
const { placeholder, inlineTags, selected, instanceId } = this.props;
const { placeholder, inlineTags, selected, instanceId, className } = this.props;
const { value = '', isActive } = this.state;
const aria = {
'aria-labelledby': this.props[ 'aria-labelledby' ],
'aria-label': this.props[ 'aria-label' ],
};
return (
<div className="woocommerce-search">
<div className={ classnames( 'woocommerce-search', className ) }>
<Gridicon className="woocommerce-search__icon" icon="search" size={ 18 } />
<Autocomplete completer={ autocompleter } onSelect={ this.selectResult }>
{ ( { listBoxId, activeId, onChange } ) =>
@ -186,6 +186,10 @@ class Search extends Component {
}
Search.propTypes = {
/**
* Class name applied to parent div.
*/
className: PropTypes.string,
/**
* Function called when selected results change, passed result list.
*/

View File

@ -78,8 +78,8 @@
"history": "^4.7.2",
"html-to-react": "^1.3.3",
"husky": "^0.14.3",
"interpolate-components": "1.1.1",
"marked": "^0.5.0",
"interpolate-components": "^1.1.1",
"node-sass": "^4.9.3",
"postcss-color-function": "^4.0.1",
"postcss-loader": "^3.0.0",