# eslint-plugin-i18n

### ESLint Rules for Translation and Internationalization:

This repository introduces ESLint rules designed to enforce best practices for internationalization (i18n) in your codebase. These rules ensure that static labels are replaced with translation functions and that all translation keys are defined in the corresponding en.json(source of truth).

**Note: This plugin has been migrated to ESLint 9 with full functionality restored using the new scope analysis API and now includes comprehensive TypeScript support.**

## Features

- ✅ **Full TypeScript Support**: Works seamlessly with `.ts` and `.tsx` files
- ✅ **Backward Compatibility**: Maintains full support for `.js` and `.jsx` files
- ✅ **ESLint 9 Compatible**: Uses modern ESLint 9 scope analysis API
- ✅ **Auto-fix Support**: Many rules include automatic fixes
- ✅ **Type-aware Analysis**: Enhanced type checking for better accuracy

## Rules overview

### 1. `no-static-labels`

#### Description:

This rule identifies and prevents the use of static labels (hardcoded strings) in JSX code. It enforces wrapping all static labels with a translation function (e.g., `t('key', 'defaultValue')`).

#### Key Features:

- Detects static strings in JSX elements, attributes, and variables.
- Identifies template literals, binary expressions, logical expressions, and conditional expressions containing static strings.
- Supports dynamic attribute checks, including menu, options, tooltip, and more.
- Auto-fix functionality to wrap detected static labels in a translation function.
- **TypeScript Support**: Works with TypeScript interfaces, types, and type annotations.

#### Why Use It?

Ensures that all user-facing text is localized, improving the scalability and maintainability of your application for internationalization.

### 2. `missing-translation`

#### Description:

This rule checks if the translation keys used in your `t` function are present in your source translation JSON file (e.g., `en.json`,). It warns developers about missing keys, ensuring translation files remain consistent with the codebase.

#### Key Features:

- Verifies translation keys passed to the `t` function.
- Supports t as a standalone function and as a method (e.g., `this.t()`).
- Helps maintain completeness of translation files.
- Warns developers if keys are missing from the source of truth JSON file (en.json).
- **TypeScript Support**: Works with TypeScript function signatures and type definitions.

#### Why Use It?

Ensures all user-facing text is properly localized and prevents runtime issues due to missing translations.

### 3. `invalid-translation-key-format`

#### Description:

This rule enforces a strict naming convention for translation keys used in the `t('key', 'defaultValue')` function to ensure consistency and predictability across translation files.

#### Convention Rules:

- All **spaces** in the fallback value must be replaced by **underscores (`_`)** in the key.
- The key must preserve the **exact casing** of the fallback string (no forced lowercase).
- No special characters allowed in the key, except a **single apostrophe (`'`)**.

### 4. `no-react-i18next-import`

#### Description:

This rule disallows the use of the useTranslation hook from react-i18next in your codebase. It enforces a centralized approach to translation using the t('key', 'fallback') pattern, instead of dynamically obtaining t via hooks. (rule specifically for sense repo)

#### Key Features:

- Detects imports of **useTranslation** from **react-i18next**.
- Reports any invocation of **useTranslation()** in the code.
- **TypeScript Support**: Works with TypeScript import statements and type declarations.

### 5. `no-dynamic-labels`

#### Description:

This rule enforces wrapping dynamic labels (variables, expressions, and computed values) in translation functions using the `makeKey` wrapper. It ensures that all dynamic content that could be user-facing is properly localized.

#### Key Features:

- Detects dynamic expressions in JSX that should be translated
- Enforces the use of `makeKey()` function for dynamic translation keys
- Supports various dynamic contexts: variables, member expressions, function calls, conditional expressions, logical expressions, and template literals
- Auto-fix functionality to wrap detected dynamic labels in `t(makeKey(dynamicValue), dynamicValue)` format
- Handles array method calls (map, filter, flatMap, reduce) and their callbacks
- Supports styled-components template expressions
- **TypeScript Support**: Enhanced type checking for better accuracy in identifying string vs non-string expressions

#### Why Use It?

Ensures that all dynamic content (variables, computed values, expressions) that could be user-facing is properly internationalized using the standardized `makeKey` pattern for dynamic translation keys.

### 6. `i18n-enforce-makeKey-wrapper`

#### Description:

This rule enforces that dynamic variables used as translation keys must be wrapped with the `makeKey()` function from the `useLabelTranslation` hook. It ensures consistent handling of dynamic translation keys across the codebase.

#### Key Features:

- Detects dynamic variables used directly as translation keys
- Enforces wrapping with `makeKey()` function from `useLabelTranslation` hook
- Validates proper hook usage and destructuring
- Auto-fix functionality to wrap dynamic keys with `makeKey()`
- Supports both direct function calls and method calls
- Configurable translation function names and hook names
- **TypeScript Support**: Works with TypeScript interfaces, types, and function signatures

#### Why Use It?

Ensures consistent and safe handling of dynamic translation keys by enforcing the use of the `makeKey` wrapper function, which provides proper key generation and validation.


## TypeScript Support

This plugin now includes comprehensive TypeScript support, making it suitable for both JavaScript and TypeScript projects.

### TypeScript Features:

- **File Extension Detection**: Automatically detects `.ts` and `.tsx` files
- **Type Annotation Support**: Understands TypeScript type annotations and interfaces
- **Enhanced Type Checking**: Better accuracy in identifying string vs non-string expressions
- **TypeScript Pragma Support**: Recognizes TypeScript-specific pragmas (`@ts-check`, `@ts-nocheck`, etc.)
- **Interface and Type Support**: Works with TypeScript interfaces, types, and type aliases
- **Generic Support**: Handles TypeScript generics and complex type expressions

### TypeScript Configuration:

```javascript
// .eslintrc.js
module.exports = {
  extends: [
    // other configurations...
  ],
  plugins: ["@spaced-out"],
  overrides: [
    {
      files: ["*.ts", "*.tsx"],
      parser: "@typescript-eslint/parser",
      parserOptions: {
        ecmaVersion: 2020,
        sourceType: "module",
        ecmaFeatures: {
          jsx: true,
        },
      },
      rules: {
        "@spaced-out/i18n/no-static-labels": "error",
        "@spaced-out/i18n/missing-translation": "warn",
        "@spaced-out/i18n/invalid-translation-key-format": "error",
        "@spaced-out/i18n/no-react-i18next-import": "error",
        "@spaced-out/i18n/no-dynamic-labels": "error",
        "@spaced-out/i18n/i18n-enforce-makeKey-wrapper": "error",
      },
    },
    {
      files: ["*.js", "*.jsx"],
      rules: {
        "@spaced-out/i18n/no-static-labels": "error",
        "@spaced-out/i18n/missing-translation": "warn",
        "@spaced-out/i18n/invalid-translation-key-format": "error",
        "@spaced-out/i18n/no-react-i18next-import": "error",
        "@spaced-out/i18n/no-dynamic-labels": "error",
        "@spaced-out/i18n/i18n-enforce-makeKey-wrapper": "error",
      },
    },
  ],
};
```

### TypeScript Usage Examples:

```typescript
// TypeScript interface example
interface User {
  name: string;
  email: string;
}

// TypeScript component with proper translations
const UserProfile: React.FC<{ user: User }> = ({ user }) => {
  return (
    <div>
      <h1>{t('user_profile_title', 'User Profile')}</h1>
      <p>{t('welcome_message', 'Welcome, {name}!', { name: user.name })}</p>
    </div>
  );
};

// TypeScript with type annotations
const getMessage = (type: 'success' | 'error'): string => {
  return t(`message_${type}`, type === 'success' ? 'Success!' : 'Error occurred');
};
```

## ESLint 9 Compatibility

This plugin has been successfully migrated to support ESLint 9 with full functionality restored using the new scope analysis API.

### Changes Made:
- **Updated scope analysis**: Replaced `context.getScope()` with ESLint 9's `context.sourceCode.scopeManager` API
- **Restored all rules**: All rules now work with full functionality using the new scope analysis
- **Enhanced error handling**: Added fallback mechanisms for scope analysis failures
- **Updated ESLint dependency**: Upgraded from ESLint 8.35.0 to ESLint 9.x
- **TypeScript support**: Added comprehensive TypeScript parsing and analysis capabilities

### Impact:
- **All rules are fully functional**: No functionality has been lost in the migration
- **Enhanced compatibility**: Better error handling and fallback mechanisms
- **Future-proof**: Uses ESLint 9's modern scope analysis API
- **TypeScript ready**: Full support for TypeScript syntax and type checking

## Installation

### 1. **Install ESLint and the plugin:**

```bash
npm install @spaced-out/eslint-plugin-i18n --save-dev
```

### 2. **For TypeScript projects, install additional dependencies:**

```bash
npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript --save-dev
```

### 3. **Add the rules to your `.eslintrc.js` configuration:**

#### For JavaScript projects:
```javascript
module.exports = {
  extends: [
    // other configurations...
  ],
  plugins: ["@spaced-out"],
  rules: {
    "@spaced-out/i18n/no-static-labels": "error",
    "@spaced-out/i18n/missing-translation": "warn",
    "@spaced-out/i18n/invalid-translation-key-format": "error",
    "@spaced-out/i18n/no-react-i18next-import": "error",
    "@spaced-out/i18n/no-dynamic-labels": "error",
    "@spaced-out/i18n/i18n-enforce-makeKey-wrapper": "error",
  },
};
```

#### For TypeScript projects:
```javascript
module.exports = {
  extends: [
    // other configurations...
  ],
  plugins: ["@spaced-out"],
  overrides: [
    {
      files: ["*.ts", "*.tsx"],
      parser: "@typescript-eslint/parser",
      // ... rest of TypeScript configuration
    }
  ]
};
```

#### Using the provided configurations:
```javascript
module.exports = {
  extends: [
    "plugin:@spaced-out/i18n/recommended", // For JavaScript
    // OR
    "plugin:@spaced-out/i18n/typescript", // For TypeScript
  ],
};
```

### 4. **Configure your translation files:**

Place your translation files (e.g., `en.json`, `fr.json`) in the translations folder inside your `i18n` directory. The rules will automatically pick up these files to validate translation keys.

## How It Works

### **Rule:** `no-static-labels`

The rule inspects your code for hardcoded static strings. For example:

**Input:**

```javascript
<div>Hello, World!</div>;
const message = "Welcome!";
<h1>{message}</h1>;
```

**Output:**
The rule suggests wrapping the strings in a translation function:

```javascript
<div>{t("Hello_World", "Hello, World!")}</div>;
const message = t("Welcome", "Welcome!");
<h1>{message}</h1>;
```

**Supported Contexts:**

- JSX Text (`<div>Static Text</div>`)
- JSX Attributes (`<button label="Click me" />`)
- JavaScript Variables (`const label = "Static text";`)
- Template Literals, Binary Expressions, Logical Expressions, Conditional Expressions, and Object Properties.
- **TypeScript**: Interfaces, types, and type annotations

### **Rule:** `missing-translation`

The rule checks translation keys used in the t function. If a key is missing in the JSON files, a warning is displayed:

**Input:**

```javascript
const message = t("NonExistentKey", "Default Message");
```

**Warning Message:**

```javascript
Translation key "NonExistentKey" is missing in the config translations JSON file.
```

**Supported Function Usages:**

- `t('key', 'defaultValue')`
- **TypeScript**: Type-safe function calls with proper type checking

### **Rule:** `invalid-translation-key-format`

This rule checks that the key passed to t() matches the fallback string (with underscores for spaces, identical casing, and only apostrophes allowed as special characters).

**Input:**

```javascript
t("youre cute", "You're cute");
```

**Output:**
The rule suggests fixing the key of wrapped translation function:

```javascript
Translation key "youre_cute" is invalid. It should be "You're_cute".
```

**Supported Contexts:**

- JSX Text (`<div>Static Text</div>`)
- JSX Attributes (`<button label="Click me" />`)
- JavaScript Variables (`const label = "Static text";`)
- Template Literals, Binary Expressions, Logical Expressions, Conditional Expressions, and Object Properties.
- **TypeScript**: Type-safe key validation

### **Rule:** `no-dynamic-labels`

This rule detects dynamic expressions that should be wrapped in translation functions using the `makeKey` pattern.

**Input:**

```javascript
const userName = "John";
const items = [{ name: "Item 1" }, { name: "Item 2" }];

<div>Hello, {userName}!</div>
<div>{items.map(item => item.name)}</div>
<div>{getLabel()}</div>
```

**Output:**
The rule suggests wrapping dynamic expressions in translation functions:

```javascript
const userName = "John";
const items = [{ name: "Item 1" }, { name: "Item 2" }];

<div>Hello, {t(makeKey(userName), userName)}!</div>
<div>{items.map(item => t(makeKey(item.name), item.name))}</div>
<div>{t(makeKey(getLabel()), getLabel())}</div>
```

**Supported Contexts:**

- Dynamic variables (`{userName}`)
- Member expressions (`{item.name}`)
- Function calls (`{getLabel()}`)
- Logical expressions (`{value || "default"}`)
- Template literals with expressions
- Array method callbacks (`items.map(item => item.name)`)
- Styled-components template expressions
- **TypeScript**: Enhanced type checking for better accuracy

### **Rule:** `i18n-enforce-makeKey-wrapper`

This rule ensures that dynamic variables used as translation keys are properly wrapped with the `makeKey()` function.

**Input:**

```javascript
const label = getLabel();
const message = t(makeKey(label), label);
```

**Output:**
The rule enforces wrapping dynamic keys with `makeKey()`:


**Required Setup:**

```javascript
// Must import and use the hook (specific to Sensehq(ui-server) usecase)
import { useLabelTranslation } from 'path/to/hook';

function Component() {
  const { t, makeKey } = useLabelTranslation();
  
  const label = getLabel();
  return <div>{t(makeKey(label), label)}</div>;
}
```

**Error Messages:**

- Missing `makeKey` wrapper: "Dynamic Variable translation key must be wrapped with makeKey() from useLabelTranslation"
- Missing hook call: "useLabelTranslation hook must be called in this component to use makeKey"
- Missing destructuring: "makeKey must be destructured from useLabelTranslation hook call"

## Fix Suggestions and Workflow

### Auto-Fix for rules that support it:

- Rules that supports auto-fix functionality. Run ESLint with the `--fix` flag to automatically wrap static labels in the `t` function.

```bash
npx eslint . --fix
```

### Handling Missing Translations

1. Run the linter to identify missing keys:

```bash
npx eslint .
```

2. Use a script to generate the missing keys in your translation files. For example:

```bash
yarn generate-translations
```

This script should:

- Parse all translation keys from the codebase.
- Compare the keys with existing translation JSON files.
- Append missing keys with default values.

## Migration Guide

### From JavaScript to TypeScript

If you're migrating your project from JavaScript to TypeScript:

1. **Install TypeScript dependencies:**
```bash
npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript --save-dev
```

2. **Update your ESLint configuration:**
```javascript
// .eslintrc.js
module.exports = {
  overrides: [
    {
      files: ["*.ts", "*.tsx"],
      parser: "@typescript-eslint/parser",
      // ... rest of TypeScript configuration
    }
  ]
};
```

3. **Add TypeScript pragmas to your files:**
```typescript
// @ts-check
import React from 'react';
```

4. **Run ESLint to check for any issues:**
```bash
npx eslint . --ext .ts,.tsx
```

### Backward Compatibility

The plugin maintains full backward compatibility with JavaScript files. All existing configurations will continue to work without any changes.

## Contributing

When contributing to this plugin, please ensure:

1. **TypeScript Support**: All new features should work with both JavaScript and TypeScript
2. **Backward Compatibility**: Changes should not break existing JavaScript functionality
3. **Type Safety**: Use TypeScript for new development when possible
4. **Testing**: Test with both `.js`/`.jsx` and `.ts`/`.tsx` files

## License

ISC
