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

Refactored @wordpress/i18n to be an instantiable #20318

Closed
wants to merge 10 commits into from
Closed
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
1 change: 1 addition & 0 deletions packages/i18n/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### New Feature

- Add `isRTL` function (#20298)
- Add `createI18n` method to allow creation of multiple i18n instances. (#20318)

## 3.1.0 (2018-11-15)

Expand Down
13 changes: 13 additions & 0 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ For a complete example, see the [Internationalization section of the Block Edito

<!-- START TOKEN(Autogenerated API docs) -->

<a name="createI18n" href="#createI18n">#</a> **createI18n**

Create an i18n instance

_Parameters_

- _initialData_ `[LocaleData]`: Locale data configuration.
- _initialDomain_ `[string]`: Domain for which configuration applies.

_Returns_

- `I18n`: I18n instance

<a name="isRTL" href="#isRTL">#</a> **isRTL**

Check if current locale is RTL.
Expand Down
195 changes: 195 additions & 0 deletions packages/i18n/src/create-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* External dependencies
*/
import Tannin from 'tannin';

/**
* @typedef {{[key: string]: any}} LocaleData
*/

/**
* Default locale data to use for Tannin domain when not otherwise provided.
* Assumes an English plural forms expression.
*
* @type {LocaleData}
*/
const DEFAULT_LOCALE_DATA = {
'': {
plural_forms: ( n ) => ( n === 1 ? 0 : 1 ),
},
};

/**
* An i18n instance
*
* @typedef {Object} I18n
* @property {Function} setLocaleData Merges locale data into the Tannin instance by domain. Accepts data in a
* Jed-formatted JSON object shape.
* @property {Function} __ Retrieve the translation of text.
* @property {Function} _x Retrieve translated string with gettext context.
* @property {Function} _n Translates and retrieves the singular or plural form based on the supplied
* number.
* @property {Function} _nx Translates and retrieves the singular or plural form based on the supplied
* number, with gettext context.
* @property {Function} isRTL Check if current locale is RTL.
*/

/**
* Create an i18n instance
*
* @param {LocaleData} [initialData] Locale data configuration.
* @param {string} [initialDomain] Domain for which configuration applies.
* @return {I18n} I18n instance
*/
export const createI18n = ( initialData, initialDomain ) => {
/**
* The underlying instance of Tannin to which exported functions interface.
*
* @type {Tannin}
*/
const tannin = new Tannin( {} );

/**
* Merges locale data into the Tannin instance by domain. Accepts data in a
* Jed-formatted JSON object shape.
*
* @see http://messageformat.github.io/Jed/
*
* @param {LocaleData} [data] Locale data configuration.
* @param {string} [domain] Domain for which configuration applies.
*/
const setLocaleData = ( data, domain = 'default' ) => {
tannin.data[ domain ] = {
...DEFAULT_LOCALE_DATA,
...tannin.data[ domain ],
...data,
};

// Populate default domain configuration (supported locale date which omits
// a plural forms expression).
tannin.data[ domain ][ '' ] = {
...DEFAULT_LOCALE_DATA[ '' ],
...tannin.data[ domain ][ '' ],
};
};

/**
* Wrapper for Tannin's `dcnpgettext`. Populates default locale data if not
* otherwise previously assigned.
*
* @param {string|undefined} domain Domain to retrieve the translated text.
* @param {string|undefined} context Context information for the translators.
* @param {string} single Text to translate if non-plural. Used as
* fallback return value on a caught error.
* @param {string} [plural] The text to be used if the number is
* plural.
* @param {number} [number] The number to compare against to use
* either the singular or plural form.
*
* @return {string} The translated string.
*/
const dcnpgettext = (
domain = 'default',
context,
single,
plural,
number
) => {
if ( ! tannin.data[ domain ] ) {
setLocaleData( undefined, domain );
}

return tannin.dcnpgettext( domain, context, single, plural, number );
};

/**
* Retrieve the translation of text.
*
* @see https://developer.wordpress.org/reference/functions/__/
*
* @param {string} text Text to translate.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} Translated text.
*/
const __ = ( text, domain ) => {
return dcnpgettext( domain, undefined, text );
};

/**
* Retrieve translated string with gettext context.
*
* @see https://developer.wordpress.org/reference/functions/_x/
*
* @param {string} text Text to translate.
* @param {string} context Context information for the translators.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} Translated context string without pipe.
*/
const _x = ( text, context, domain ) => {
return dcnpgettext( domain, context, text );
};

/**
* Translates and retrieves the singular or plural form based on the supplied
* number.
*
* @see https://developer.wordpress.org/reference/functions/_n/
*
* @param {string} single The text to be used if the number is singular.
* @param {string} plural The text to be used if the number is plural.
* @param {number} number The number to compare against to use either the
* singular or plural form.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} The translated singular or plural form.
*/
const _n = ( single, plural, number, domain ) => {
return dcnpgettext( domain, undefined, single, plural, number );
};

/**
* Translates and retrieves the singular or plural form based on the supplied
* number, with gettext context.
*
* @see https://developer.wordpress.org/reference/functions/_nx/
*
* @param {string} single The text to be used if the number is singular.
* @param {string} plural The text to be used if the number is plural.
* @param {number} number The number to compare against to use either the
* singular or plural form.
* @param {string} context Context information for the translators.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} The translated singular or plural form.
*/
const _nx = ( single, plural, number, context, domain ) => {
return dcnpgettext( domain, context, single, plural, number );
};

/**
* Check if current locale is RTL.
*
* **RTL (Right To Left)** is a locale property indicating that text is written from right to left.
* For example, the `he` locale (for Hebrew) specifies right-to-left. Arabic (ar) is another common
* language written RTL. The opposite of RTL, LTR (Left To Right) is used in other languages,
* including English (`en`, `en-US`, `en-GB`, etc.), Spanish (`es`), and French (`fr`).
*
* @return {boolean} Whether locale is RTL.
*/
const isRTL = () => {
return 'rtl' === _x( 'ltr', 'text direction' );
};

setLocaleData( initialData, initialDomain );
Copy link
Member

Choose a reason for hiding this comment

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

Should we always be calling this, even if initialData and/or initialDomain are possibly undefined (per the documented types)?

Copy link
Member

Choose a reason for hiding this comment

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

Related to this, are there specific use-cases in mind for setting the initial data? It seems like something which could be useful, but just wondering if we might be getting ahead of ourselves.

Copy link
Member

Choose a reason for hiding this comment

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

I requested this. It seems helpful to be able to populate with initial data, but there's not much lost if we require package consumers to first create then set the data. I don't have strong feelings.

Copy link
Member

Choose a reason for hiding this comment

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

It's still odd to me that we call setLocaleData if there is nothing to set. It works, but only by a combination of the fact that data is an optional argument of setLocaleData (pretty counter-intuitive), and that object spread is tolerant of undefined (i.e. { ...undefined }), when instead we could just add an if here, and make setLocaleData's data argument non-optional.

Suggested change
setLocaleData( initialData, initialDomain );
if ( initialData ) {
setLocaleData( initialData, initialDomain );
}

Copy link
Member

Choose a reason for hiding this comment

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

@jameslnewell Do you mind addressing this?

Copy link
Member

Choose a reason for hiding this comment

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

I addressed this in #21182. I believe that's the remaining feedback that needed to be addressed.


return {
setLocaleData,
__,
_x,
_n,
_nx,
isRTL,
};
};
96 changes: 96 additions & 0 deletions packages/i18n/src/default-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Internal dependencies
*/
import { createI18n } from './create-i18n';

const i18n = createI18n();

/*
* Comments in this file are duplicated from ./i18n due to
* https://github.com/WordPress/gutenberg/pull/20318#issuecomment-590837722
*/

/**
* @typedef {{[key: string]: any}} LocaleData
*/

/**
* Merges locale data into the Tannin instance by domain. Accepts data in a
* Jed-formatted JSON object shape.
*
* @see http://messageformat.github.io/Jed/
*
* @param {LocaleData} [data] Locale data configuration.
* @param {string} [domain] Domain for which configuration applies.
*/
export const setLocaleData = i18n.setLocaleData.bind( i18n );

/**
* Retrieve the translation of text.
*
* @see https://developer.wordpress.org/reference/functions/__/
*
* @param {string} text Text to translate.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} Translated text.
*/
export const __ = i18n.__.bind( i18n );

/**
* Retrieve translated string with gettext context.
*
* @see https://developer.wordpress.org/reference/functions/_x/
*
* @param {string} text Text to translate.
* @param {string} context Context information for the translators.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} Translated context string without pipe.
*/
export const _x = i18n._x.bind( i18n );

/**
* Translates and retrieves the singular or plural form based on the supplied
* number.
*
* @see https://developer.wordpress.org/reference/functions/_n/
*
* @param {string} single The text to be used if the number is singular.
* @param {string} plural The text to be used if the number is plural.
* @param {number} number The number to compare against to use either the
* singular or plural form.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} The translated singular or plural form.
*/
export const _n = i18n._n.bind( i18n );

/**
* Translates and retrieves the singular or plural form based on the supplied
* number, with gettext context.
*
* @see https://developer.wordpress.org/reference/functions/_nx/
*
* @param {string} single The text to be used if the number is singular.
* @param {string} plural The text to be used if the number is plural.
* @param {number} number The number to compare against to use either the
* singular or plural form.
* @param {string} context Context information for the translators.
* @param {string} [domain] Domain to retrieve the translated text.
*
* @return {string} The translated singular or plural form.
*/
export const _nx = i18n._nx.bind( i18n );

/**
* Check if current locale is RTL.
*
* **RTL (Right To Left)** is a locale property indicating that text is written from right to left.
* For example, the `he` locale (for Hebrew) specifies right-to-left. Arabic (ar) is another common
* language written RTL. The opposite of RTL, LTR (Left To Right) is used in other languages,
* including English (`en`, `en-US`, `en-GB`, etc.), Spanish (`es`), and French (`fr`).
*
* @return {boolean} Whether locale is RTL.
*/
export const isRTL = i18n.isRTL.bind( i18n );
Loading