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

First pass at I18N-specific ESLint rules #20555

Merged
merged 47 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e8656b9
Add new ESLint rule to validate text domains
swissspidy Feb 28, 2020
80f6b8b
Enforce `@wordpress/valid-text-domain` rule for Gutenberg code base
swissspidy Feb 28, 2020
656ec12
Use messageId and make fixable
swissspidy Feb 29, 2020
6beedd1
Add new no-missing-translator-comments rule
swissspidy Feb 29, 2020
d366d59
Enforce `@wordpress/no-missing-translator-comments` rule for Gutenber…
swissspidy Feb 29, 2020
6c4e07c
Add docs
swissspidy Feb 29, 2020
f1b325b
Merge master branch and rename rules
swissspidy Mar 11, 2020
05ee583
Implement feedback from code review
swissspidy Mar 11, 2020
3af1fb5
Rename rule names
swissspidy Mar 11, 2020
82c956d
Simplify getting previousArg
swissspidy Mar 11, 2020
d611c85
Extract and document utils
swissspidy Mar 11, 2020
8194ffa
Combine comments
swissspidy Mar 11, 2020
be5a596
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 11, 2020
6125f28
Derive allowDefault from allowedTextDomains
swissspidy Mar 11, 2020
797053d
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 19, 2020
02e30c4
Break early for line number mismatches
swissspidy Mar 19, 2020
225c09d
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 19, 2020
11569f3
Support `i18n.*` usage in new rules
swissspidy Mar 19, 2020
d1b2fec
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 24, 2020
c48aa0a
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 25, 2020
9e535ce
Add new i18n-no-variables rule
swissspidy Mar 24, 2020
811c951
Add new i18n-ellipsis rule
swissspidy Mar 25, 2020
210759d
Add new i18n-no-placeholders-only rule
swissspidy Mar 25, 2020
4511fd2
Add new i18n-no-collapsible-whitespace rule
swissspidy Mar 25, 2020
bee529e
Disable i18n-no-collapsible-whitespace rule for now
swissspidy Mar 26, 2020
b2fcda3
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 26, 2020
d388f13
Remove unneded capture group
swissspidy Mar 27, 2020
67e38e5
Use Set for list of translation functions
swissspidy Mar 27, 2020
545790b
Move const to top scope
swissspidy Mar 27, 2020
05a8920
Coding standards in code examples
swissspidy Mar 27, 2020
f845bae
Refactor utils to make code more DRY
swissspidy Mar 27, 2020
63c8606
Coding standards in test code
swissspidy Mar 27, 2020
63fd381
Remove now unneeded no-restricted-syntax config
swissspidy Mar 27, 2020
e6fe285
Add i18n rules to new i18n config
swissspidy Mar 27, 2020
5ceb0c7
Mark new ruleset as breaking change
swissspidy Mar 27, 2020
a2e16d5
Update docs
swissspidy Mar 27, 2020
4c2d632
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 27, 2020
5bcac71
Fix tests
swissspidy Mar 30, 2020
3c05054
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Mar 30, 2020
e7187a0
Rename argument to allowedTextDomain and allow strings and arrays
swissspidy Mar 31, 2020
887ed2d
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Apr 1, 2020
071e9b8
Apply suggestions from code review
swissspidy Apr 1, 2020
a9c2301
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Apr 1, 2020
948f22a
Turn on i18n-no-collapsible-whitespace rule by default
swissspidy Apr 1, 2020
684cc2d
Merge branch 'master' into add/i18n-eslint-rules
swissspidy Apr 2, 2020
dc94c94
Fix format after applying suggested change
swissspidy Apr 2, 2020
6153feb
Reset lastIndex after using test() on a regex with global flag
swissspidy Apr 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 7 additions & 23 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ module.exports = {
'@wordpress/dependency-group': 'error',
'@wordpress/gutenberg-phase': 'error',
'@wordpress/react-no-unsafe-timeout': 'error',
'@wordpress/i18n-text-domain': [
'error',
{
allowedTextDomain: 'default',
},
],
'@wordpress/i18n-no-collapsible-whitespace': 'off',
'no-restricted-syntax': [
'error',
// NOTE: We can't include the forward slash in our regex or
Expand All @@ -67,29 +74,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.',
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -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
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
7 changes: 4 additions & 3 deletions packages/block-library/src/missing/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@ export class UnsupportedBlockEdit extends Component {
styles.infoSheetIconDark
);

// translators: %s: Name of the block
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
const titleFormat =
Platform.OS === 'android'
? __( "'%s' isn't yet supported on WordPress for Android" )
: __( "'%s' isn't yet supported on WordPress for iOS" );
? // translators: %s: Name of the block
__( "'%s' isn't yet supported on WordPress for Android" )
: // translators: %s: Name of the block
__( "'%s' isn't yet supported on WordPress for iOS" );
const infoTitle = sprintf( titleFormat, title );

return (
Expand Down
8 changes: 7 additions & 1 deletion packages/block-library/src/post-author/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ function PostAuthorDisplay() {
[ authorId ]
);
return author ? (
<address>{ sprintf( __( 'By %s' ), author.name ) }</address>
<address>
{ sprintf(
/* translators: %s: author name. */
__( 'By %s' ),
author.name
) }
</address>
) : null;
}

Expand Down