Skip to content

Commit

Permalink
First pass at I18N-specific ESLint rules (#20555)
Browse files Browse the repository at this point in the history
* Add new ESLint rule to validate text domains

* Enforce `@wordpress/valid-text-domain` rule for Gutenberg code base

* Use messageId and make fixable

* Add new no-missing-translator-comments rule

* Enforce `@wordpress/no-missing-translator-comments` rule for Gutenberg code base

* Add docs

* Implement feedback from code review

* Rename rule names

* Simplify getting previousArg

* Extract and document utils

* Combine comments

* Derive allowDefault from allowedTextDomains

* Break early for line number mismatches

Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com>

* Support `i18n.*` usage in new rules

* Add new i18n-no-variables rule

* Add new i18n-ellipsis rule

* Add new i18n-no-placeholders-only rule

* Add new i18n-no-collapsible-whitespace rule

* Disable i18n-no-collapsible-whitespace rule for now

* Remove unneded capture group

* Use Set for list of translation functions

* Move const to top scope

* Coding standards in code examples

* Refactor utils to make code more DRY

* Coding standards in test code

* Remove now unneeded no-restricted-syntax config

* Add i18n rules to new i18n config

* Mark new ruleset as breaking change

* Update docs

* Fix tests

* Rename argument to allowedTextDomain and allow strings and arrays

* Apply suggestions from code review

Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com>

* Turn on i18n-no-collapsible-whitespace rule by default

* Fix format after applying suggested change

* Reset lastIndex after using test() on a regex with global flag

Co-authored-by: Andrew Duthie <andrew@andrewduthie.com>
  • Loading branch information
swissspidy and aduth committed Apr 2, 2020
1 parent 1e21633 commit 4bbfbc1
Show file tree
Hide file tree
Showing 74 changed files with 1,511 additions and 128 deletions.
29 changes: 6 additions & 23 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ module.exports = {
'@wordpress/dependency-group': 'error',
'@wordpress/gutenberg-phase': 'error',
'@wordpress/react-no-unsafe-timeout': 'error',
'@wordpress/i18n-text-domain': [
'error',
{
allowedTextDomain: 'default',
},
],
'no-restricted-syntax': [
'error',
// NOTE: We can't include the forward slash in our regex or
Expand All @@ -67,29 +73,6 @@ module.exports = {
message:
'Deprecated functions must be removed before releasing this version.',
},
{
selector:
'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])',
message:
'Translate function arguments must be string literals.',
},
{
selector:
'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])',
message:
'Translate function arguments must be string literals.',
},
{
selector:
'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])',
message:
'Translate function arguments must be string literals.',
},
{
selector:
'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]',
message: 'Use ellipsis character (…) in place of three dots',
},
{
selector:
'ImportDeclaration[source.value="redux"] Identifier.imported[name="combineReducers"]',
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions packages/block-directory/src/components/block-ratings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ export const BlockRatings = ( { rating, ratingCount } ) => (
<Stars rating={ rating } />
<span
className="block-directory-block-ratings__rating-count"
aria-label={
aria-label={ sprintf(
// translators: %d: number of ratings (number).
sprintf(
_n( '%d total rating', '%d total ratings', ratingCount ),
ratingCount
)
}
_n( '%d total rating', '%d total ratings', ratingCount ),
ratingCount
) }
>
({ ratingCount })
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ function Stars( { rating } ) {
const emptyStarCount = 5 - ( fullStarCount + halfStarCount );

return (
<div aria-label={ sprintf( __( '%s out of 5 stars' ), stars ) }>
<div
aria-label={ sprintf(
/* translators: %s: number of stars. */
__( '%s out of 5 stars' ),
stars
) }
>
{ times( fullStarCount, ( i ) => (
<Icon
key={ `full_stars_${ i }` }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ function DownloadableBlockAuthorInfo( {
return (
<Fragment>
<span className="block-directory-downloadable-block-author-info__content-author">
{ sprintf( __( 'Authored by %s' ), author ) }
{ sprintf(
/* translators: %s: author name. */
__( 'Authored by %s' ),
author
) }
</span>
<span className="block-directory-downloadable-block-author-info__content">
{ sprintf(
/* translators: 1: number of blocks. 2: average rating. */
_n(
'This author has %d block, with an average rating of %d.',
'This author has %d blocks, with an average rating of %d.',
'This author has %1$d block, with an average rating of %2$d.',
'This author has %1$d blocks, with an average rating of %2$d.',
authorBlockCount
),
authorBlockCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ function DownloadableBlockHeader( {
return (
<div className="block-directory-downloadable-block-header__row">
{ icon.match( /\.(jpeg|jpg|gif|png)(?:\?.*)?$/ ) !== null ? (
// translators: %s: Name of the plugin e.g: "Akismet".
<img
src={ icon }
alt={ sprintf( __( '%s block icon' ), title ) }
alt={ sprintf(
// translators: %s: Name of the plugin e.g: "Akismet".
__( '%s block icon' ),
title
) }
/>
) : (
<span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function DownloadableBlockInfo( {
<div className="block-directory-downloadable-block-info__column">
<Icon icon={ chartLine }></Icon>
{ sprintf(
/* translators: %s: number of active installations. */
_n(
'%d active installation',
'%d active installations',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function DownloadableBlocksPanel( {
}

const resultsFoundMessage = sprintf(
/* translators: %s: number of available blocks. */
_n(
'No blocks found in your library. We did find %d block available for download.',
'No blocks found in your library. We did find %d blocks available for download.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export function getBlockMoverDescription(
}

if ( isFirst && isLast ) {
// translators: %s: Type of block (i.e. Text, Image etc)
return sprintf(
// translators: %s: Type of block (i.e. Text, Image etc)
__( 'Block %s is the only block, and cannot be moved' ),
type
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export class BlockSwitcher extends Component {
1 === blocks.length
? __( 'Change block type or style' )
: sprintf(
/* translators: %s: number of blocks. */
_n(
'Change type of %d block',
'Change type of %d blocks',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ function ButtonBlockAppender( {
} ) => {
let label;
if ( hasSingleBlockType ) {
// translators: %s: the name of the block when there is only one
label = sprintf(
// translators: %s: the name of the block when there is only one
_x( 'Add %s', 'directly add the only allowed block' ),
blockTitle
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ function InserterBlockList( {
);

const resultsFoundMessage = sprintf(
/* translators: %d: number of results. */
_n( '%d result found.', '%d results found.', resultCount ),
resultCount
);
Expand Down
4 changes: 2 additions & 2 deletions packages/block-editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const defaultRenderToggle = ( {
} ) => {
let label;
if ( hasSingleBlockType ) {
// translators: %s: the name of the block when there is only one
label = sprintf(
// translators: %s: the name of the block when there is only one
_x( 'Add %s', 'directly add the only allowed block' ),
blockTitle
);
Expand Down Expand Up @@ -236,8 +236,8 @@ export default compose( [
);

if ( ! selectBlockOnInsert ) {
// translators: %s: the name of the block that has been added
const message = sprintf(
// translators: %s: the name of the block that has been added
__( '%s block added' ),
allowedBlockType.title
);
Expand Down
6 changes: 5 additions & 1 deletion packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,11 @@ function LinkControl( {
const searchResultsLabelId = `block-editor-link-control-search-results-label-${ instanceId }`;
const labelText = isInitialSuggestions
? __( 'Recently updated' )
: sprintf( __( 'Search results for "%s"' ), inputValue );
: sprintf(
/* translators: %s: search term. */
__( 'Search results for "%s"' ),
inputValue
);

// VisuallyHidden rightly doesn't accept custom classNames
// so we conditionally render it as a wrapper to visually hide the label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const LinkControlSearchCreate = ( {
<span className="block-editor-link-control__search-item-title">
{ createInterpolateElement(
sprintf(
/* translators: %s: search term. */
__( 'New page: <mark>%s</mark>' ),
searchTerm
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class MediaUploadProgress extends React.Component {
const { isUploadInProgress, isUploadFailed } = this.state;
const showSpinner = this.state.isUploadInProgress;
const progress = this.state.progress * 100;
// eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace
const retryMessage = __(
'Failed to insert media.\nPlease tap for options.'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ function MultiSelectionInspector( { blocks } ) {
/>
<div className="block-editor-multi-selection-inspector__card-content">
<div className="block-editor-multi-selection-inspector__card-title">
{ /* translators: %d: number of blocks */
sprintf(
{ sprintf(
/* translators: %d: number of blocks */
_n( '%d block', '%d blocks', blocks.length ),
blocks.length
) }
</div>
<div className="block-editor-multi-selection-inspector__card-description">
{ /* translators: %d: number of words */
sprintf( _n( '%d word', '%d words', words ), words ) }
{ sprintf(
/* translators: %d: number of words */
_n( '%d word', '%d words', words ),
words
) }
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ function ResponsiveBlockControl( props ) {
isResponsive = false,
defaultLabel = {
id: 'all',
label: __(
'All'
) /* translators: 'Label. Used to signify a layout property (eg: margin, padding) will apply uniformly to all screensizes.' */,
/* translators: 'Label. Used to signify a layout property (eg: margin, padding) will apply uniformly to all screensizes.' */
label: __( 'All' ),
},
viewports = [
{
Expand All @@ -50,10 +49,13 @@ function ResponsiveBlockControl( props ) {
return null;
}

/* translators: 'Toggle control label. Should the property be the same across all screen sizes or unique per screen size.'. %s property value for the control (eg: margin, padding...etc) */
const toggleControlLabel =
toggleLabel ||
sprintf( __( 'Use the same %s on all screensizes.' ), property );
sprintf(
/* translators: 'Toggle control label. Should the property be the same across all screen sizes or unique per screen size.'. %s property value for the control (eg: margin, padding...etc) */
__( 'Use the same %s on all screensizes.' ),
property
);

/* translators: 'Help text for the responsive mode toggle control.' */
const toggleHelpText = __(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function ResponsiveBlockControlLabel( {
const accessibleLabel =
desc ||
sprintf(
/* translators: 1: property name. 2: viewport name. */
_x(
'Controls the %1$s property for %2$s viewports.',
'Text labelling a interface as controlling a given layout property (eg: margin) for a given screen size.'
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/url-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class URLInput extends Component {
if ( !! suggestions.length ) {
this.props.debouncedSpeak(
sprintf(
/* translators: %s: number of results. */
_n(
'%d result found, use up and down arrow keys to navigate.',
'%d results found, use up and down arrow keys to navigate.',
Expand Down
2 changes: 1 addition & 1 deletion packages/block-editor/src/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ export default {
MULTI_SELECT: ( action, { getState } ) => {
const blockCount = getSelectedBlockCount( getState() );

/* translators: %s: number of selected blocks */
speak(
sprintf(
/* translators: %s: number of selected blocks */
_n( '%s block selected.', '%s blocks selected.', blockCount ),
blockCount
),
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/code/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export const settings = {
icon,
example: {
attributes: {
/* eslint-disable @wordpress/i18n-no-collapsible-whitespace */
// translators: Preserve \n markers for line breaks
content: __(
'// A "block" is the abstract term used\n// to describe units of markup that\n// when composed together, form the\n// content or layout of a page.\nregisterBlockType( name, settings );'
),
/* eslint-enable @wordpress/i18n-no-collapsible-whitespace */
},
},
supports: {
Expand Down
6 changes: 3 additions & 3 deletions packages/block-library/src/embed/embed-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ class EmbedPreview extends Component {
.splice( parsedHost.length - 2, parsedHost.length - 1 )
.join( '.' );
const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedHostBaseUrl );
// translators: %s: host providing embed content e.g: www.youtube.com
const iframeTitle = sprintf(
// translators: %s: host providing embed content e.g: www.youtube.com
__( 'Embedded content from %s' ),
parsedHostBaseUrl
);
Expand Down Expand Up @@ -125,8 +125,8 @@ class EmbedPreview extends Component {
<a href={ url }>{ url }</a>
</p>
<p className="components-placeholder__error">
{ /* translators: %s: host providing embed content e.g: www.youtube.com */
sprintf(
{ sprintf(
/* translators: %s: host providing embed content e.g: www.youtube.com */
__(
"Embedded content from %s can't be previewed in the editor."
),
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/gallery/gallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export const Gallery = ( props ) => {
>
<ul className="blocks-gallery-grid">
{ images.map( ( img, index ) => {
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
const ariaLabel = sprintf(
/* translators: 1: the order number of the image. 2: the total number of images. */
__( 'image %1$d of %2$d in gallery' ),
index + 1,
images.length
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/gallery/gallery.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export const Gallery = ( props ) => {
}
>
{ images.map( ( img, index ) => {
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
const ariaLabel = sprintf(
/* translators: 1: the order number of the image. 2: the total number of images. */
__( 'image %1$d of %2$d in gallery' ),
index + 1,
images.length
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ export class ImageEdit extends Component {
defaultedAlt = alt;
} else if ( filename ) {
defaultedAlt = sprintf(
/* translators: %s: file name */
__(
'This image has an empty alt attribute; its file name is %s'
),
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/missing/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function MissingBlockWarning( { attributes, convertToHTML } ) {
let messageHTML;
if ( hasContent && hasHTMLBlock ) {
messageHTML = sprintf(
/* translators: %s: block name */
__(
'Your site doesn’t include support for the "%s" block. You can leave this block intact, convert its content to a Custom HTML block, or remove it entirely.'
),
Expand All @@ -29,6 +30,7 @@ function MissingBlockWarning( { attributes, convertToHTML } ) {
);
} else {
messageHTML = sprintf(
/* translators: %s: block name */
__(
'Your site doesn’t include support for the "%s" block. You can leave this block intact or remove it entirely.'
),
Expand Down

0 comments on commit 4bbfbc1

Please sign in to comment.