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

Framework: Add localization helpers to global wpcom instance #3348

Merged
merged 2 commits into from
Feb 18, 2016
Merged
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
3 changes: 3 additions & 0 deletions client/boot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var config = require( 'config' ),
accessibleFocus = require( 'lib/accessible-focus' ),
TitleStore = require( 'lib/screen-title/store' ),
renderWithReduxStore = require( 'lib/react-helpers' ).renderWithReduxStore,
bindWpLocaleState = require( 'lib/wp/localization' ).bindState,
// The following components require the i18n mixin, so must be required after i18n is initialized
Layout;

Expand Down Expand Up @@ -160,6 +161,8 @@ function boot() {
function reduxStoreReady( reduxStore ) {
let layoutSection, layout, layoutElement, validSections = [];

bindWpLocaleState( reduxStore );

if ( config.isEnabled( 'support-user' ) ) {
require( 'lib/user/support-user-interop' )( reduxStore );
}
Expand Down
14 changes: 9 additions & 5 deletions client/lib/wp/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const debug = debugFactory( 'calypso:wp' );
import wpcomUndocumented from 'lib/wpcom-undocumented';
import config from 'config';
import wpcomSupport from 'lib/wp/support';
import { injectLocalization } from './localization';

const addSyncHandlerWrapper = config.isEnabled( 'sync-handler' );
let wpcom;
Expand Down Expand Up @@ -40,11 +41,14 @@ if ( config.isEnabled( 'oauth' ) ) {
} );
}

if ( config.isEnabled( 'support-user' ) ) {
wpcom = wpcomSupport( wpcom );
}

// Inject localization helpers to `wpcom` instance
wpcom = injectLocalization( wpcom );

/**
* Expose `wpcom`
*/
if ( config.isEnabled( 'support-user' ) ) {
module.exports = wpcomSupport( wpcom );
} else {
module.exports = wpcom;
}
module.exports = wpcom;
10 changes: 10 additions & 0 deletions client/lib/wp/localization/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
REPORTER ?= spec
NODE_BIN := ../../../../node_modules/.bin
MOCHA ?= $(NODE_BIN)/mocha
BASE_DIR := $(NODE_BIN)/../..
NODE_PATH := $(BASE_DIR)/client

test:
@NODE_ENV=test NODE_PATH=$(NODE_PATH) $(MOCHA) --compilers jsx:babel/register,js:babel/register --reporter $(REPORTER)

.PHONY: test
16 changes: 16 additions & 0 deletions client/lib/wp/localization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
wpcom.js Localization
=====================

This module enables the extension of a `wpcom.js` instance to include localization helper functions. Specifically, the modified instance will include a new `withLocale` function, which will result in the subsequent chained request being localized according to the current user's preferred locale.

## Usage

The helper is already bound for the global instance of `wpcom.js` used in Calypso. To take advantage of the localization helpers, call the `withLocale` function at the start of your request chain.

```js
import wpcom from 'lib/wp';

wpcom.withLocale().site( siteId ).postTypesList().then( ( data ) => {
// `data` is a localized response
} );
```
74 changes: 74 additions & 0 deletions client/lib/wp/localization/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* External dependencies
*/
import qs from 'querystring';

/**
* Internal dependencies
*/
import { getCurrentUserLocale } from 'state/current-user/selectors';

/**
* Module variables
*/
let locale;

/**
* Given a WPCOM parameter set, modifies the query such that a non-default
* locale is added to the query parameter.
*
* @param {Object} params Original parameters
Copy link
Contributor

Choose a reason for hiding this comment

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

add - ?. I've gotten this suggestion before from @rralian

/**
 * @param  {Object} params - Original parameters
 */

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The hyphen itself is optional, and is available to help readability (reference), though a similar effect is achieved via aligning spaces on the start of the type, name, and description.

Usage seems a bit mixed throughout Calypso. In my own case, I'm using the Sublime Text 3 DocBlockr package, which does not add the hyphens and (as far as I can tell) does not offer an option to do so, so it's a bit more effort for me to revise. TL;DR: I'm lazy 😆

Copy link
Contributor

Choose a reason for hiding this comment

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

:-D

* @return {Object} Revised parameters, if non-default locale
*/
export function addLocaleQueryParam( params ) {
if ( ! locale || 'en' === locale ) {
return params;
}

let query = qs.parse( params.query );
return Object.assign( params, {
query: qs.stringify( Object.assign( query, { locale } ) )
} );
};

/**
* Modifies a WPCOM instance, returning an updated instance with included
* localization helpers. Specifically, this adds a new `withLocale` method to
* the base instance for indicating the request should be localized.
*
* @param {Object} wpcom Original WPCOM instance
* @return {Object} Modified WPCOM instance with localization helpers
*/
export function injectLocalization( wpcom ) {
const request = wpcom.request.bind( wpcom );
return Object.assign( wpcom, {
withLocale: function() {
this.localize = true;
return this;
},

request: function( params, callback ) {
if ( this.localize ) {
this.localize = false;
return request( addLocaleQueryParam( params ), callback );
}

return request( params, callback );
}
} );
}

/**
* Subscribes to the provided Redux store instance, updating the known locale
* value to the latest value when state changes.
*
* @param {Object} store Redux store instance
*/
export function bindState( store ) {
function setLocale() {
locale = getCurrentUserLocale( store.getState() );
}

store.subscribe( setLocale );
setLocale();
}
146 changes: 146 additions & 0 deletions client/lib/wp/localization/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* External dependencies
*/
import { expect } from 'chai';
import rewire from 'rewire';
import mockery from 'mockery';
import sinon from 'sinon';

describe( 'localization', () => {
let localization, addLocaleQueryParam, injectLocalization, bindState;
let getCurrentUserLocaleMock = sinon.stub();

before( () => {
// Mock user locale state selector
mockery.enable( {
warnOnReplace: false,
warnOnUnregistered: false
} );
mockery.registerMock( 'state/current-user/selectors', {
getCurrentUserLocale: () => getCurrentUserLocaleMock()
} );

// Prepare module for rewiring
localization = rewire( '../' );
addLocaleQueryParam = localization.addLocaleQueryParam;
injectLocalization = localization.injectLocalization;
bindState = localization.bindState;
} );

beforeEach( () => {
localization.__set__( 'locale', undefined );
} );

after( function() {
mockery.disable();
} );

describe( '#addLocaleQueryParam()', () => {
it( 'should not modify params if locale unknown', () => {
const params = addLocaleQueryParam( { query: 'search=foo' } );

expect( params ).to.eql( { query: 'search=foo' } );
} );

it( 'should not modify params if locale is default', () => {
localization.__set__( 'locale', 'en' );
const params = addLocaleQueryParam( { query: 'search=foo' } );

expect( params ).to.eql( { query: 'search=foo' } );
} );

it( 'should include the locale query parameter for a non-default locale', () => {
localization.__set__( 'locale', 'fr' );
const params = addLocaleQueryParam( { query: 'search=foo' } );

expect( params ).to.eql( {
query: 'search=foo&locale=fr'
} );
} );
} );

describe( '#injectLocalization()', () => {
it( 'should return a modified object', () => {
let wpcom = { request() {} };
injectLocalization( wpcom );

expect( wpcom.withLocale ).to.be.a( 'function' );
} );

it( 'should override the default request method', () => {
const request = () => {};
let wpcom = { request };
injectLocalization( wpcom );

expect( wpcom.request ).to.not.equal( request );
} );

it( 'should not modify params if `withLocale` not used', ( done ) => {
localization.__set__( 'locale', 'fr' );
let wpcom = {
request( params ) {
expect( params.query ).to.equal( 'search=foo' );
done();
}
};

injectLocalization( wpcom );
wpcom.request( { query: 'search=foo' } );
} );

it( 'should modify params if `withLocale` is used', ( done ) => {
localization.__set__( 'locale', 'fr' );
let wpcom = {
request( params ) {
expect( params.query ).to.equal( 'search=foo&locale=fr' );
done();
}
};

injectLocalization( wpcom );
wpcom.withLocale().request( { query: 'search=foo' } );
} );

it( 'should not modify the request after `withLocale` is used', ( done ) => {
localization.__set__( 'locale', 'fr' );
let assert = false;
let wpcom = {
request( params ) {
if ( ! assert ) {
return;
}

expect( params.query ).to.equal( 'search=foo' );
done();
}
};

injectLocalization( wpcom );
wpcom.withLocale().request( { query: 'search=foo' } );
assert = true;
wpcom.request( { query: 'search=foo' } );
} );
} );

describe( '#bindState()', () => {
it( 'should set initial locale from state', () => {
getCurrentUserLocaleMock = sinon.stub().returns( 'fr' );
bindState( { subscribe() {}, getState() {} } );
expect( localization.__get__( 'locale' ) ).to.equal( 'fr' );
} );

it( 'should subscribe to the store, setting locale on change', () => {
let listener;
bindState( {
subscribe( _listener ) {
listener = _listener;
},
getState() {}
} );
getCurrentUserLocaleMock = sinon.stub().returns( 'de' );
listener();

expect( localization.__get__( 'locale' ) ).to.equal( 'de' );
} );
} );
} );
10 changes: 7 additions & 3 deletions client/lib/wp/node.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/**
* Internal dependencies
*/
var wpcom = require( 'lib/wpcom-undocumented' );
var config = require( 'config' );
import wpcomUndocumented from 'lib/wpcom-undocumented';
import config from 'config';
import { injectLocalization } from './localization';

wpcom = wpcom( require( 'wpcom-xhr-request' ) );
let wpcom = wpcomUndocumented( require( 'wpcom-xhr-request' ) );

if ( config.isEnabled( 'support-user' ) ) {
wpcom = require( 'lib/wp/support' )( wpcom );
}

// Inject localization helpers to `wpcom` instance
wpcom = injectLocalization( wpcom );

module.exports = wpcom;
15 changes: 15 additions & 0 deletions client/state/current-user/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,18 @@ export function getCurrentUser( state ) {

return getUser( state, state.currentUser.id );
}

/**
* Returns the locale slug for the current user.
*
* @param {Object} state Global state tree
* @return {?String} Current user locale
*/
export function getCurrentUserLocale( state ) {
const user = getCurrentUser( state );
if ( ! user ) {
return null;
}

return user.localeSlug || null;
}
47 changes: 46 additions & 1 deletion client/state/current-user/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { expect } from 'chai';
/**
* Internal dependencies
*/
import { getCurrentUser } from '../selectors';
import {
getCurrentUser,
getCurrentUserLocale
} from '../selectors';

describe( 'selectors', () => {
describe( '#getCurrentUser()', () => {
Expand Down Expand Up @@ -35,4 +38,46 @@ describe( 'selectors', () => {
expect( selected ).to.eql( { ID: 73705554, login: 'testonesite2014' } );
} );
} );

describe( '#getCurrentUserLocale', () => {
it( 'should return null if the current user is not set', () => {
const locale = getCurrentUserLocale( {
currentUser: {
id: null
}
} );

expect( locale ).to.be.null;
} );

it( 'should return null if the current user locale slug is not set', () => {
const locale = getCurrentUserLocale( {
users: {
items: {
73705554: { ID: 73705554, login: 'testonesite2014' }
}
},
currentUser: {
id: 73705554
}
} );

expect( locale ).to.be.null;
} );

it( 'should return the current user locale slug', () => {
const locale = getCurrentUserLocale( {
users: {
items: {
73705554: { ID: 73705554, login: 'testonesite2014', localeSlug: 'fr' }
}
},
currentUser: {
id: 73705554
}
} );

expect( locale ).to.equal( 'fr' );
} );
} );
} );