Skip to content

ESLint plugin to enforce specific code standards in internationalization practices, particularly focusing on attribute strings and template literals in JSX.

License

sanity-io/eslint-plugin-i18n

Repository files navigation

@sanity/eslint-plugin-i18n

This ESLint plugin provides rules to enforce specific code standards in internationalization practices, particularly focusing on attribute strings and template literals in JSX. It aims to improve code quality and maintainability in projects with internationalization concerns.

Installation

πŸ‘‹ Note: We highly recommend installing @sanity/eslint-config-i18n instead which comes bundled with other plugins that are useful for localizing your Studio.

Ensure ESLint is installed and initialized then install the following package:

npm install @sanity/eslint-plugin-i18n --save-dev

Then update your eslint configuration:

// .eslintrc.cjs
module.exports = {
  plugins: ['@sanity/i18n'],
  rules: {
    '@sanity/i18n/no-attribute-string-literals': 'error',
    '@sanity/i18n/no-attribute-template-literals': 'error',
    '@sanity/i18n/no-i18next-import': 'error',
  },
};

Rules

no-attribute-string-literals

This rule finds string literals within JSX attributes. These attribute can be nested within ternary expression and logical expressions.

Note: This rule does not report if there is an expression within template literals as this is what no-attribute-template-literals is for.

Examples of incorrect code for this rule:

// πŸ›‘ JSX literal
<MyComponent prop="some string literal">
// πŸ›‘ string literal inside of a JSX expression container
<MyComponent prop={'some string literal'}>
// πŸ›‘ template literal with no expressions in a JSX expression container
<MyComponent prop={`some string literal`}>
// πŸ›‘ string literals nested within a ternary
<MyComponent prop={conditional ? 'nested literal' : 'another literal'}>
// πŸ›‘ string literals nested within a logical expression
<MyComponent prop={nullishValue ?? 'fallback literal'}>

Examples of correct code for this rule:

// βœ… function call
<MyComponent text={t('some.localized.text')}>
// βœ… not string literals
<MyComponent booleanAttr numberAttr={5}>

no-attribute-template-literals

This rule finds template literals with expressions within JSX attributes. These attribute can be nested within ternary expression and logical expressions.

Note: With the default configuration, this rule will only report if there is whitespace within a template element. See usage below to change this behavior.

Examples of incorrect code for this rule:

// πŸ›‘ template literal with an expression and whitespace
<MyComponent prop={`Hello ${name}!`}>
// πŸ›‘ template literals with expressions nested within a ternary
<MyComponent prop={conditional ? `Hello ${name}` : `Goodbye ${name}`}>
// πŸ›‘ template literals with expression nested within a logical expression
<MyComponent prop={nullishValue ?? `Hello ${name}`}>

Examples of correct code for this rule:

// βœ… does not contain an expression (this will break the other rule)
<MyComponent text={`no expression`}>
// βœ… no whitespace in the template element (with the default config)
<MyComponent id={`prefix-${id}`}>
// βœ… not string literals
<MyComponent booleanAttr numberAttr={5}>

no-i18next-import

This rule finds imports of the i18next and react-i18next package. While these packages are (currently) used as dependencies of sanity, this is considered an implementation detail and should not be relied upon. Instead, you should import any i18n utility/helper from the sanity module directly.

Examples of incorrect code for this rule:

// πŸ›‘ importing from `react-i18next``
import { useTranslation } from 'react-i18next';
// πŸ›‘ requiring from `react-i18next``
const { Trans } = require('react-i18next');

Examples of correct code for this rule:

// βœ… requiring from `sanity`
const { Translate } = require('sanity');

Usage

In your ESLint config, add "@sanity/i18n" to the list of plugins and enable the rules:

// .eslintrc.cjs
module.exports = {
  plugins: ['@sanity/i18n'],
  rules: {
    '@sanity/i18n/no-attribute-string-literals': [
      'error',
      // these take in the same options.
      // you may omit this if you don't want to configure any more options.
      {
        ignores: {
          // ...
        },
        only: {
          // ...
        },
        mode: 'extend',
      },
    ],
    '@sanity/i18n/no-attribute-template-literals': [
      'error',
      // these take in the same options.
      // you may omit this if you don't want to configure any more options.
      {
        ignores: {
          // ...
        },
        only: {
          // ...
        },
        mode: 'extend',
      },
    ],
    '@sanity/i18n/no-i18next-import': 'error',
  },
};

Rule options

Option Description Type Default value
mode If the mode is specified as override the default configuration for ignores and only will be overridden by the user configuration. The default behavior is to extend. 'extend' | 'override' 'extend'
only If specified, only attributes matching its conditions will be considered. If omitted, all attributes are initially selected. InputOption See here
ignores This is the primary filter used to specify exceptions or conditions for which attributes should not be reported. Attributes matching its conditions are excluded from being reported. InputOption See here

Note: If the only option is present with ignores, the only option will determine which attributes to consider and then the ignores option is used to ignore any attributes that match.

InputOption

The only and ignores options take in this InputOption type shown below. Note how you can combine logic expressions to consider and ignore different attributes.

type InputOption =
  | { and: InputOption[] }
  | { or: InputOption[] }
  | { not: InputOption }
  | {
      /**
       * List of attributes to match.
       *
       * For instance, to match the attribute `foo` in `<Component foo="text" />`,
       * add "foo" to this list.
       */
      attributes?: string[];

      /**
       * Regex patterns for attribute names. (case insensitive)
       *
       * E.g., to match all `data-` attributes in `<Component data-testid="a" />`,
       * use the pattern "^data-\w+".
       */
      attributePatterns?: string[];

      /**
       * List of string literals or template element values to match.
       *
       * For instance, to match the value `hello ` in `<Component text={`hello ${name}`} />`,
       * add `'hello '` to this list.
       */
      values?: string[];

      /**
       * Regex patterns to match string literals or template element values.
       * (case insensitive)
       *
       * For instance, to match the value `id:` in `<Component text={`id:${name}`} />`,
       * add `'id:'` to this list.
       */
      valuePatterns?: string[];

      /**
       * List of component names to match.
       *
       * For instance, to match the `<Button />` component,
       * add "Button" to this list.
       */
      components?: string[];

      /**
       * Regex patterns for component names. (case insensitive)
       *
       * E.g., to match any component with "Btn" in its name like `<SubmitBtn />` or `<CancelBtn />`,
       * use the pattern ".*btn".
       */
      componentPatterns?: string[];
    };

There are three logical operators to aid in crafting your criteria:

  • OR (or): Any condition within the or block being true makes the entire block true.
  • AND (and): All conditions within the and block must be true for the entire block to be true.
  • NOT (not): Inverts the result of its condition.

When multiple keys are present in the input object, they follow the "OR" logic. If you desire an "AND" relationship, use the and condition explicitly. For example, to enforce that an attribute must be from the Button component AND named 'as', you'd use:

{
  "and": [{ "components": ["Button"] }, { "attributes": ["as"] }]
}

See the defaults for more examples.

Additionally the default options can be imported via @sanity/eslint-plugin-i18n/defaults

const {
  defaultNoAttributeTemplateLiteralsOptions,
  defaultNoAttributeStringLiteralsOptions,
} = require('@sanity/eslint-plugin-i18n/defaults');

About

ESLint plugin to enforce specific code standards in internationalization practices, particularly focusing on attribute strings and template literals in JSX.

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks