diff --git a/README.md b/README.md
index 9c807db..c58206c 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ yarn add eslint-plugin-lingui --dev
### Recommended Setup
-To enable all of the recommended rules for our plugin, add the following config:
+To enable all the recommended rules for our plugin, add the following config:
```js
import pluginLingui from 'eslint-plugin-lingui'
@@ -47,6 +47,8 @@ export default [
]
```
+We also recommend enabling the [no-unlocalized-strings](docs/rules/no-unlocalized-strings.md) rule. It’s not enabled by default because it needs to be set up specifically for your project. Please check the rule's documentation for example configurations.
+
### Custom setup
Alternatively, you can load the plugin and configure only the rules you want to use:
diff --git a/docs/rules/no-unlocalized-strings.md b/docs/rules/no-unlocalized-strings.md
index cd658a6..836fcd0 100644
--- a/docs/rules/no-unlocalized-strings.md
+++ b/docs/rules/no-unlocalized-strings.md
@@ -5,18 +5,78 @@ Ensures that all string literals, templates, and JSX text are wrapped using `
[!IMPORTANT]
> This rule may require TypeScript type information. Enable this feature by setting `{ useTsTypes: true }`.
+This rule is designed to **match all** JSXText, StringLiterals, and TmplLiterals, and then exclude some of them based on attributes, property names, variable names, and so on.
+
+The rule doesn’t come with built-in ignore settings because each project is unique and needs different configurations. You can use the following config as a starting point and then adjust it for your project:
+
+
+```json5
+{
+ "no-unlocalized-strings": [
+ "error",
+ {
+ "ignore": [
+ // Ignore strings that don’t start with an uppercase letter
+ // or don't contain two words separated by whitespace
+ "^(?![A-Z].*|\\w+\\s\\w+).+$",
+ // Ignore UPPERCASE literals
+ // Example: const test = "FOO"
+ "^[A-Z0-9_-]+$"
+ ],
+ "ignoreNames": [
+ // Ignore matching className (case-insensitive)
+ { "regex": { "pattern": "className", "flags": "i" } },
+ // Ignore UPPERCASE names
+ // Example: test.FOO = "ola!"
+ { "regex": { "pattern": "^[A-Z0-9_-]+$" } },
+ "styleName",
+ "src",
+ "srcSet",
+ "type",
+ "id",
+ "width",
+ "height",
+ "displayName",
+ "Authorization"
+ ],
+ "ignoreFunctions": [
+ "cva",
+ "cn",
+ "track",
+ "Error",
+ "console.*",
+ "*headers.set",
+ "*.addEventListener",
+ "*.removeEventListener",
+ "*.postMessage",
+ "*.getElementById",
+ "*.dispatch",
+ "*.commit",
+ "*.includes",
+ "*.indexOf",
+ "*.endsWith",
+ "*.startsWith",
+ "require"
+ ],
+ // Following settings require typed linting https://typescript-eslint.io/getting-started/typed-linting/
+ "useTsTypes": true,
+ "ignoreMethodsOnType": [
+ // Ignore specified methods on Map and Set types
+ "Map.get",
+ "Map.has",
+ "Set.has"
+ ]
+ }
+ ]
+}
+```
+
## Options
### `useTsTypes`
Enables the rule to use TypeScript type information. Requires [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) to be configured.
-This option automatically excludes built-in methods such as `Map` and `Set`, and cases where string literals are used as TypeScript constants, e.g.:
-
-```ts
-const a: 'abc' = 'abc'
-```
-
### `ignore`
Specifies patterns for string literals to ignore. Strings matching any of the provided regular expressions will not trigger the rule.
@@ -28,17 +88,17 @@ Example for `{ "ignore": ["rgba"] }`:
const color =
```
-### `ignoreFunction`
+### `ignoreFunctions`
Specifies functions whose string arguments should be ignored.
Example of `correct` code with this option:
```js
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["showIntercomMessage"]}]*/
+/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["showIntercomMessage"]}]*/
showIntercomMessage('Please write me')
-/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunction": ["cva"] }]*/
+/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunctions": ["cva"] }]*/
const labelVariants = cva('text-form-input-content-helper', {
variants: {
size: {
@@ -49,131 +109,60 @@ const labelVariants = cva('text-form-input-content-helper', {
})
```
-This option also supports member expressions. Example for `{ "ignoreFunction": ["console.log"] }`:
+This option also supports member expressions. Example for `{ "ignoreFunctions": ["console.log"] }`:
```js
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["console.log"]}]*/
+/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["console.log"]}]*/
console.log('Log this message')
```
You can use patterns (processed by [micromatch](https://www.npmjs.com/package/micromatch)) to match function calls.
```js
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["console.*"]}]*/
+/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["console.*"]}]*/
console.log('Log this message')
```
```js
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["*.headers.set"]}]*/
+/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["*.headers.set"]}]*/
context.headers.set('Authorization', `Bearer ${token}`)
```
Dynamic segments are replaced with `$`, you can target them as
```js
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["foo.$.set"]}]*/
+/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["foo.$.set"]}]*/
foo[getName()].set('Hello')
```
-### `ignoreAttribute`
-
-Specifies JSX attributes that should be ignored. By default, the attributes `className`, `styleName`, `type`, `id`, `width`, and `height` are ignored.
-
-Example for `{ "ignoreAttribute": ["style"] }`:
-
-```jsx
-/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreAttribute": ["style"]}]*/
-const element =
-```
-
-#### `regex`
-
-Defines regex patterns for ignored attributes.
-
-Example:
-
-```json
-{
- "no-unlocalized-strings": [
- "error",
- {
- "ignoreAttribute": [
- {
- "regex": {
- "pattern": "classname",
- "flags": "i"
- }
- }
- ]
- }
- ]
-}
-```
-
-Example of **correct** code:
-
-```jsx
-const element =
-```
-
-### `strictAttribute`
-
-Specifies JSX attributes that should always be checked, regardless of other `ignore` settings or defaults.
-
-Example for `{ "strictAttribute": ["alt"] }`:
+### `ignoreNames`
-```jsx
-/*eslint lingui/no-unlocalized-strings: ["error", {"strictAttribute": ["alt"]}]*/
-const element =
-```
-
-#### `regex`
-
-Defines regex patterns for attributes that must always be checked.
-
-Example:
+List of identifier names to ignore across attributes, properties, and variables. Use this option to exclude specific names, like "className", from being flagged by the rule. This option covers any of these contexts: JSX attribute names, variable names, or property names.
-```json
-{
- "no-unlocalized-strings": [
- "error",
- {
- "strictAttribute": [
- {
- "regex": {
- "pattern": "^desc.*"
- }
- }
- ]
- }
- ]
-}
-```
+Example for `{ "ignoreNames": ["style"] }`:
-Examples of **incorrect** code:
+Example of `correct` code with this option:
```jsx
-const element =
-```
-
-### `ignoreProperty`
-
-Specifies object property names whose values should be ignored. By default, UPPERCASED properties and `className`, `styleName`, `type`, `id`, `width`, `height`, and `displayName` are ignored.
-
-Example for `{ "ignoreProperty": ["myProperty"] }`:
+/* eslint lingui/no-unlocalized-strings: ["error", {"ignoreNames": ["style"]}] */
+// ignored by JSX sttribute name
+const element =
+// ignored by variable name
+const style = 'Ignored value!'
-```jsx
-const obj = { myProperty: 'Ignored value' }
-obj.myProperty = 'Ignored value'
+/* eslint lingui/no-unlocalized-strings: ["error", {"ignoreNames": ["displayName"]}] */
+// ignored by property name
+const obj = { displayName: 'Ignored value' }
+obj.displayName = 'Ignored value'
class MyClass {
- myProperty = 'Ignored value'
+ displayName = 'Ignored value'
}
```
#### `regex`
-Defines regex patterns for ignored properties.
+Defines regex patterns for ignored names.
Example:
@@ -182,7 +171,7 @@ Example:
"no-unlocalized-strings": [
"error",
{
- "ignoreProperty": [
+ "ignoreNames": [
{
"regex": {
"pattern": "classname",
@@ -195,9 +184,16 @@ Example:
}
```
-Examples of **correct** code:
+Example of **correct** code:
```jsx
+// ignored by JSX attribute name
+const element =
+
+// ignored by variable name
+const wrapperClassName = 'Ignored value'
+
+// ignored by property name
const obj = { wrapperClassName: 'Ignored value' }
obj.wrapperClassName = 'Ignored value'
@@ -206,46 +202,6 @@ class MyClass {
}
```
-### `ignoreVariable`
-
-Specifies variable name whose values should be ignored. By default, UPPERCASED variables are ignored.
-
-Example for `{ "ignoreVariable": ["myVariable"] }`:
-
-```jsx
-const myVariable = 'Ignored value'
-```
-
-#### `regex`
-
-Defines regex patterns for ignored variables.
-
-Example:
-
-```json
-{
- "no-unlocalized-strings": [
- "error",
- {
- "ignoreVariable": [
- {
- "regex": {
- "pattern": "classname",
- "flags": "i"
- }
- }
- ]
- }
- ]
-}
-```
-
-Examples of **correct** code:
-
-```jsx
-const wrapperClassName = 'Ignored value'
-```
-
### `ignoreMethodsOnTypes`
Uses TypeScript type information to ignore methods defined on specific types.
@@ -264,5 +220,3 @@ interface Foo {
const foo: Foo
foo.get('Some string')
```
-
-The following methods are ignored by default: `Map.get`, `Map.has`, `Set.has`.
diff --git a/src/helpers.ts b/src/helpers.ts
index a085b04..16655ec 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -29,8 +29,6 @@ export const LinguiCallExpressionMessageQuery =
*/
export const LinguiTransQuery = 'JSXElement[openingElement.name.name=Trans]'
-export const UpperCaseRegexp = /^[A-Z_-]+$/
-
export function isNativeDOMTag(str: string) {
return DOM_TAGS.includes(str)
}
@@ -39,27 +37,13 @@ export function isSvgTag(str: string) {
return SVG_TAGS.includes(str)
}
-export function containAllAttributes(attributeNames: string[]) {
- const attrs = ['value', 'other']
- return attrs.every((attr) => attributeNames.includes(attr))
-}
-
-export function isLinguiTags(str: string, attributeNames: string[]) {
- if (str === 'Trans') {
- return true
- }
- return ['Plural', 'Select'].includes(str) && containAllAttributes(attributeNames)
-}
-
const blacklistAttrs = ['placeholder', 'alt', 'aria-label', 'value']
export function isAllowedDOMAttr(tag: string, attr: string, attributeNames: string[]) {
if (isSvgTag(tag)) return true
if (isNativeDOMTag(tag)) {
return !blacklistAttrs.includes(attr)
}
- if (isLinguiTags(tag, attributeNames)) {
- return true
- }
+
return false
}
@@ -87,26 +71,6 @@ export const getText = (
return node.value.toString().trim()
}
-export function hasAncestorWithName(
- node: TSESTree.JSXElement | TSESTree.TemplateLiteral | TSESTree.Literal | TSESTree.JSXText,
- name: string,
-) {
- let p: TSESTree.Node = node.parent
- while (p) {
- switch (p.type) {
- case TSESTree.AST_NODE_TYPES.JSXElement:
- const identifierName = getIdentifierName(p?.openingElement?.name)
- if (identifierName === name) {
- return true
- }
- default:
- }
-
- p = p.parent
- }
- return false
-}
-
export function getIdentifierName(jsxTagNameExpression: TSESTree.JSXTagNameExpression) {
switch (jsxTagNameExpression.type) {
case TSESTree.AST_NODE_TYPES.JSXIdentifier:
diff --git a/src/rules/no-unlocalized-strings.ts b/src/rules/no-unlocalized-strings.ts
index 3c63d4f..24d244c 100644
--- a/src/rules/no-unlocalized-strings.ts
+++ b/src/rules/no-unlocalized-strings.ts
@@ -9,14 +9,12 @@ import {
getIdentifierName,
getNearestAncestor,
getText,
- hasAncestorWithName,
isAllowedDOMAttr,
isIdentifier,
isJSXAttribute,
isLiteral,
isMemberExpression,
isTemplateLiteral,
- UpperCaseRegexp,
} from '../helpers'
import { createRule } from '../create-rule'
import * as micromatch from 'micromatch'
@@ -25,11 +23,8 @@ type MatcherDef = string | { regex: { pattern: string; flags?: string } }
export type Option = {
ignore?: string[]
- ignoreFunction?: string[]
- ignoreAttribute?: MatcherDef[]
- strictAttribute?: MatcherDef[]
- ignoreProperty?: MatcherDef[]
- ignoreVariable?: MatcherDef[]
+ ignoreFunctions?: string[]
+ ignoreNames?: MatcherDef[]
ignoreMethodsOnTypes?: string[]
useTsTypes?: boolean
}
@@ -62,15 +57,13 @@ const MatcherSchema: JSONSchema.JSONSchema4 = {
],
}
-function preparePatterns(items: MatcherDef[]): (string | RegExp)[] {
- return items.map((item) =>
+function createMatcher(patterns: MatcherDef[]) {
+ const _patterns = patterns.map((item) =>
typeof item === 'string' ? item : new RegExp(item.regex.pattern, item.regex.flags),
)
-}
-function createMatcher(patterns: (string | RegExp)[]) {
return (str: string) => {
- return patterns.some((pattern) => {
+ return _patterns.some((pattern) => {
if (typeof pattern === 'string') {
return pattern === str
}
@@ -105,7 +98,11 @@ export const rule = createRule