Skip to content

camelohq/eslint-plugin-i18n-rules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

53 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

eslint-plugin-i18n-rules

ESLint rules to prevent hardcoded, user-visible strings in React/JSX and encourage proper internationalization.

Overview

This plugin provides comprehensive ESLint rules to catch hardcoded strings in JSX that should be internationalized. It helps maintain consistent i18n practices by detecting user-visible text in both JSX content and attributes.

Features

  • πŸš€ JSX Text Detection: Catches hardcoded strings in JSX elements and expression containers
  • 🎯 JSX Attributes Validation: Detects hardcoded strings in accessibility and user-facing attributes
  • 🧠 Smart Filtering: Ignores whitespace, punctuation-only content, and non-user-visible elements
  • βš™οΈ Configurable: Opt-in rules with granular control
  • πŸ“ TypeScript Support: Full TypeScript compatibility

Install

Install directly from GitHub using a commit hash:

npm install --save-dev camelohq/eslint-plugin-i18n-rules#commit-hash
# or
yarn add --dev camelohq/eslint-plugin-i18n-rules#commit-hash

Replace commit-hash with the specific commit you want to use.

Usage

Basic Configuration

Add to your ESLint config:

{
  "plugins": ["i18n-rules"],
  "rules": {
    "i18n-rules/no-hardcoded-jsx-text": "error",
    "i18n-rules/no-hardcoded-jsx-attributes": "warn"
  }
}

Recommended Config

Use the plugin's recommended setup, which enables no-hardcoded-jsx-text as an error and no-hardcoded-jsx-attributes as a warning:

{
  "extends": ["plugin:i18n-rules/recommended"]
}

JavaScript/TypeScript Config

module.exports = {
  plugins: ["i18n-rules"],
  rules: {
    "i18n-rules/no-hardcoded-jsx-text": "error",
    "i18n-rules/no-hardcoded-jsx-attributes": "warn", // Start with warnings
  },
};

Rules

no-hardcoded-jsx-text βœ… Recommended

Prevents hardcoded strings in JSX text content and expression containers.

❌ Invalid

<div>Hello World</div>                    // Direct text
<span>{"Welcome back"}</span>             // String literal in expression
<p>{`Static message`}</p>                 // Template literal (no expressions)
<button>Save</button>                     // Button text

βœ… Valid

<div>{t('hello.world')}</div>             // i18n function
<span>{t('welcome.back')}</span>          // Internationalized
<p>{`Hello ${userName}`}</p>              // Dynamic template literal
<Trans>Welcome {name}</Trans>             // i18n component
<div>{" "}</div>                          // Whitespace (ignored)
<title>Page Title</title>                 // HTML metadata (ignored)
<Trans i18nKey="welcome">Welcome to our app</Trans>  // Trans content ignored

no-hardcoded-jsx-attributes πŸ†•

Detects hardcoded strings in user-visible JSX attributes that should be internationalized.

Targeted Attributes

  • Accessibility: aria-label, aria-description, aria-valuetext, aria-roledescription
  • User-facing: title, alt, placeholder

❌ Invalid

<button aria-label="Save document" />     // Accessibility label
<img alt="User profile picture" />        // Image alt text
<input placeholder="Enter your name" />   // Form placeholder
<div title="Click to expand" />           // Tooltip text
<div aria-description="Helpful info" />   // ARIA description

βœ… Valid

<button aria-label={t('actions.save')} />           // i18n function
<img alt={t('user.profilePicture')} />              // Internationalized
<input placeholder={t('forms.enterName')} />        // Proper i18n
<div aria-labelledby="heading-id" />                // ID reference (allowed)
<div aria-describedby="description-id" />           // ID reference (allowed)
<div title="πŸŽ‰" />                                  // Emoji only (ignored)
<Layout title="Page Title" />                       // Wrapper component (ignored by default)
<SEO title="Welcome | My App" />                    // SEO component (ignored by default)

Configuration Options

{
  "rules": {
    "i18n-rules/no-hardcoded-jsx-attributes": [
      "warn",
      {
        "ignoreLiterals": ["404", "N/A", "SKU-0001"],
        "caseSensitive": false,
        "trim": true,
        "ignoreComponentsWithTitle": ["Layout", "SEO"]
      }
    ]
  }
}
  • ignoreLiterals (string[], default: ["404", "N/A"]) - Array of string literals to ignore
  • caseSensitive (boolean, default: false) - Case-sensitive matching for ignore literals
  • trim (boolean, default: true) - Trim whitespace before comparing ignore literals
  • ignoreComponentsWithTitle (string[], default: ["Layout", "SEO"]) - Components where hardcoded title props are allowed

Smart Detection

The plugin intelligently filters out content that doesn't need internationalization:

Ignored Content

  • Whitespace-only: <div> </div>, <span>{" "}</span>
  • Punctuation/Symbols: <div>β€” β€’ βœ“</div>, <span>{"..."}</span>
  • Emojis: <div>πŸŽ‰πŸš€</div>, <button>{"😊"}</button>
  • HTML Metadata: <title>, <style>, <script> content
  • Trans Components: Content inside <Trans> components from next-i18next
  • ID References: aria-labelledby, aria-describedby attributes

Dynamic Content (Allowed)

  • Template literals with expressions: <div>{Hello ${name}}</div>
  • Function calls: <span>{formatDate(date)}</span>
  • Variables: <p>{userMessage}</p>

Examples

Before (❌ Hardcoded)

function UserProfile({ user }) {
  return (
    <Layout title="User Dashboard">
      {" "}
      {/* Layout title allowed by default */}
      <h1>User Profile</h1>
      <img
        src={user.avatar}
        alt="Profile picture" // ❌ Hardcoded alt text
        title="Click to change avatar" // ❌ Hardcoded tooltip
      />
      <button aria-label="Edit profile">Edit</button>{" "}
      {/* ❌ Hardcoded aria-label */}
      <p>Welcome back, {user.name}!</p> {/* ❌ Hardcoded text */}
    </Layout>
  );
}

After (βœ… Internationalized)

function UserProfile({ user }) {
  return (
    <Layout title="User Dashboard">
      {" "}
      {/* βœ… Layout title still allowed */}
      <h1>{t("profile.title")}</h1>
      <img
        src={user.avatar}
        alt={t("profile.picture")}
        title={t("profile.changeAvatar")}
      />
      <button aria-label={t("actions.editProfile")}>{t("actions.edit")}</button>
      <p>{t("welcome.back", { name: user.name })}</p>
    </Layout>
  );
}

Documentation

Development

# Install dependencies
yarn install

# Build the plugin
yarn build

# Run tests
yarn test

# Run linting
yarn lint

Requirements

  • ESLint: ^8.0.0
  • TypeScript: ~5.0.4 (for development)
  • Node.js: >= 14

Contributing

Contributions are welcome! Please read our contributing guidelines and ensure all tests pass before submitting a pull request.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors