-
Notifications
You must be signed in to change notification settings - Fork 4.8k
ESLint: Add no-i18n-in-save rule #75617
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
Merged
Merged
Changes from 2 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
bd3dd4f
Add no-i18n-in-save rule
mikachan 6af390f
Add tests
mikachan d934ee7
Exclude deprecated files
mikachan 980bfa6
Normalize filenames before checking paths
mikachan 0e1f51a
Move new rule to local eslintrc file
mikachan 39f4571
Update packages/eslint-plugin/docs/rules/no-i18n-in-save.md
mikachan 9cb4d16
Update packages/eslint-plugin/rules/__tests__/no-i18n-in-save.js
mikachan 1cdbe80
Use counter in insideSaveFunction instead of boolean
mikachan 919243e
Merge branch 'trunk' into add/eslint-rule-translations-in-save
mikachan 2c4749b
Move rule definition to block-library
mikachan 90caaec
Update readme
mikachan 7bbe256
Remove true recommendation
mikachan db73885
Soften the when not to use info
mikachan 4399554
Move rule to root file
mikachan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Disallow translation functions in block save methods (no-i18n-in-save) | ||
|
|
||
| Translation functions should never be used in block `save` functions or `save.js` files. | ||
|
|
||
| ## Rule details | ||
|
|
||
| This rule aims to prevent the use of i18n translation functions (`__`, `_x`, `_n`, `_nx`) in block save methods. | ||
|
|
||
| ### Why? | ||
|
|
||
| When translation functions are used in save methods: | ||
|
|
||
| 1. Translation is saved to database: The translated text is stored at the time of saving, not when the content is displayed | ||
| 2. No dynamic updates: If the site language changes, previously saved content will not update | ||
| 3. Block validation errors: Switching languages causes validation errors because the saved HTML no longer matches what the save function generates | ||
| 4. Content locked to language: Content becomes permanently associated with the language active at save time | ||
|
|
||
| ### What to do instead | ||
|
|
||
| - For static text: Use plain English text that PHP can replace during rendering | ||
| - For dynamic labels: Use block attributes and let PHP handle translation during rendering | ||
| - For user content: Store as attributes and render/translate in PHP | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Incorrect | ||
|
|
||
| ```js | ||
| // ❌ Translation in save function | ||
| function save() { | ||
| return ( | ||
| <button> | ||
| <span>{ __( 'Click me', 'my-plugin' ) }</span> | ||
| </button> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ```js | ||
| // ❌ Translation in arrow function save | ||
| const save = () => { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| ``` | ||
|
|
||
| ```js | ||
| // ❌ Translation in object method | ||
| const settings = { | ||
| save() { | ||
| return _x( 'Label', 'context' ); | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| ### Correct | ||
|
|
||
| ```js | ||
| // ✅ No translation in save, handled by PHP | ||
| function save() { | ||
| return ( | ||
| <button> | ||
| <span>Click me</span> | ||
| </button> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ```js | ||
| // ✅ Translation in edit function | ||
| function edit() { | ||
| return ( | ||
| <button> | ||
| <span>{ __( 'Click me', 'my-plugin' ) }</span> | ||
| </button> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ```js | ||
| // ✅ Use attributes and let PHP translate | ||
| function save( { attributes } ) { | ||
| return ( | ||
| <button> | ||
| <span>{ attributes.label }</span> | ||
| </button> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## When not to use | ||
|
|
||
| This rule should not be disabled. If you think you need an exception, consider: | ||
|
|
||
| 1. Using a render callback in PHP to handle translation | ||
| 2. Storing translatable content in block attributes | ||
| 3. Using static text that PHP replaces during rendering | ||
|
|
||
| ## Further Reading | ||
|
|
||
| - [Block API Reference](https://developer.wordpress.org/block-editor/reference-guides/block-api/) | ||
| - [Internationalization in WordPress](https://developer.wordpress.org/apis/internationalization/) | ||
222 changes: 222 additions & 0 deletions
222
packages/eslint-plugin/rules/__tests__/no-i18n-in-save.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import { RuleTester } from 'eslint'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import rule from '../no-i18n-in-save'; | ||
|
|
||
| const ruleTester = new RuleTester( { | ||
| parserOptions: { | ||
| ecmaVersion: 6, | ||
| sourceType: 'module', | ||
| ecmaFeatures: { | ||
| jsx: true, | ||
| }, | ||
| }, | ||
| } ); | ||
|
|
||
| ruleTester.run( 'no-i18n-in-save', rule, { | ||
| valid: [ | ||
| { | ||
| code: ` | ||
| function edit() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| const edit = () => { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| edit() { | ||
| return __( 'Hello World' ); | ||
| }, | ||
| }; | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation functions are fine in non-save files | ||
| function render() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation in edit function | ||
| export default function Edit() { | ||
| return <div>{ __( 'Hello World' ) }</div>; | ||
| } | ||
| `, | ||
| }, | ||
| ], | ||
| invalid: [ | ||
| { | ||
| code: ` | ||
|
mikachan marked this conversation as resolved.
|
||
| function save() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const save = () => { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const save = function() { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| export default function save() { | ||
| return <span>{ __( 'Hello World' ) }</span>; | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| save() { | ||
| return __( 'Hello World' ); | ||
| }, | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| save: () => __( 'Hello World' ), | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| return _x( 'Hello', 'greeting' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| const count = 5; | ||
| return _n( 'One item', 'Multiple items', count ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| const count = 5; | ||
| return _nx( 'One item', 'Multiple items', count, 'context' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| return ( | ||
| <button> | ||
| <span>{ __( 'Click me' ) }</span> | ||
| </button> | ||
| ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| // Multiple translation calls in save | ||
| function save() { | ||
| const label = __( 'Label' ); | ||
| return <div title={ _x( 'Title', 'context' ) }>{ label }</div>; | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| } ); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.