An open API service indexing awesome lists of open source software.

https://github.com/bartoszjarocki/jscodeshift-react-i18next

jscodeshift transform that aims to extract hardocded strings in React applications
https://github.com/bartoszjarocki/jscodeshift-react-i18next

ast codemod i18next jscodeshift

Last synced: 5 months ago
JSON representation

jscodeshift transform that aims to extract hardocded strings in React applications

Awesome Lists containing this project

README

          

# React i18n Codemod

A jscodeshift transform that automatically extracts hardcoded strings from React components and wraps them in translation hooks. This tool helps automate the internationalization process of React applications.

## Features

- ✨ Extracts text from JSX elements
- 🔤 Handles translatable attributes (alt, title, aria-label, etc.)
- 📝 Supports template literals with variables
- 🔑 Automatically generates translation keys
- 💾 Updates translation JSON files
- 🔄 Adds necessary imports and hooks
- 📦 TypeScript support

## Installation

```bash
npm install -g jscodeshift
npm install --save-dev @types/jscodeshift
```

## Usage

```bash
npx jscodeshift -t transform.ts src/**/*.tsx --parser=tsx \
--translationFilePath=./translations.json \
--importName=react-i18next
```

before

```tsx
import React from 'react';

interface User {
name: string;
role: string;
notifications: number;
}

const user: User = {
name: 'John Doe',
role: 'Admin',
notifications: 5
};

const formatDate = (date: Date) => date.toLocaleDateString();
const currentDate = new Date();

export const TestComponent: React.FC = () => {
return (


{/* Simple text translation */}

Welcome to Our Platform


Please read the instructions carefully

{/* Translatable attributes */}
User profile picture

{/* Template literals with variables */}


{`Hello ${user.name}, you are logged in as ${user.role}`}

{/* Multiple variables and nested properties */}


{`You have ${user.notifications} new notifications as of ${formatDate(currentDate)}`}

{/* Mixed content */}

Account Overview


{`Last login: ${formatDate(currentDate)}`}



Account Status: Active

{/* Complex template literals */}


{`Your subscription ${user.notifications > 0 ? 'requires' : 'does not require'} attention`}

{/* Multiple elements with translatable content */}

Thank you for using our service



Need help? Contact support

{`© ${new Date().getFullYear()} All rights reserved`}



);
};
```

after

```tsx
import { useTranslation } from 'react-i18next';
import React from 'react';

interface User {
name: string;
role: string;
notifications: number;
}

const user: User = {
name: 'John Doe',
role: 'Admin',
notifications: 5
};

const formatDate = (date: Date) => date.toLocaleDateString();
const currentDate = new Date();

export const TestComponent: React.FC = () => {
const {
t
} = useTranslation();

return (
(


{/* Simple text translation */}

{t('testComponent.welcome-to-our-platform')}


{t('testComponent.please-read-the-instructions-carefully')}


{/* Translatable attributes */}
{t('testComponent.user-profile-picture')}

{/* Template literals with variables */}

{t('testComponent.hello-you-are-logged-in-as', {
name: user.name,
role: user.role
})}

{/* Multiple variables and nested properties */}

{t('testComponent.you-have-new-notifications-as-of', {
notifications: user.notifications,
var2: formatDate(currentDate)
})}

{/* Mixed content */}

{t('testComponent.account-overview')}


{t('testComponent.last-login', {
var1: formatDate(currentDate)
})}


{t('testComponent.account-status-active')}


{/* Complex template literals */}


{t('testComponent.your-subscription-attention', {
var1: user.notifications > 0 ? 'requires' : 'does not require'
})}


{/* Multiple elements with translatable content */}

{t('testComponent.thank-you-for-using-our-service')}


{t('testComponent.need-help-contact-support')}
{t('testComponent.c-all-rights-reserved', {
var1: new Date().getFullYear()
})}


)
);
};
```

and translation file created

```json
{
"testComponent": {
"account-overview": "Account Overview",
"account-status-active": "Account Status: Active",
"c-all-rights-reserved": "© {{var1}} All rights reserved",
"click-to-edit-your-profile-picture": "Click to edit your profile picture",
"contact-support": "Contact support",
"get-help-from-our-support-team": "Get help from our support team",
"hello-you-are-logged-in-as": "Hello {{name}}, you are logged in as {{role}}",
"last-login": "Last login: {{var1}}",
"need-help-contact-support": "Need help? Contact support",
"notification-count": "Notification count",
"please-read-the-instructions-carefully": "Please read the instructions carefully",
"subscription-status": "Subscription status",
"thank-you-for-using-our-service": "Thank you for using our service",
"this-should-be-translated": "This SHOULD be translated",
"this-template-string-text-should-be-tran": "This template string text should be translated too, {{v1}}, and {{v2}} and that's it.",
"this-text-should-be-translated-too": "This text should be translated too",
"user-profile-picture": "User profile picture",
"username-input-field": "Username input field",
"username-must-be-at-least-3-characters": "Username must be at least 3 characters",
"welcome-to-our-platform": "Welcome to Our Platform",
"you-have-new-notifications-as-of": "You have {{notifications}} new notifications as of {{var2}}",
"your-subscription-attention": "Your subscription {{var1}} attention"
}
}
```

### Options

- `translationFilePath` (required): Path to your translations JSON file
- `translationRoot` (optional): Root key in translations file (e.g., 'en' or 'translations')
- `importName` (required): Translation package to import (e.g., 'react-i18next', 'next-i18next')

## Examples

### Basic Text Translation

Input:

```tsx
function Welcome() {
return

Hello World
;
}
```

Output:

```tsx
import { useTranslation } from 'react-i18next';

function Welcome() {
const { t } = useTranslation();
return

{t('welcome.hello-world')}
;
}
```

### Attribute Translation

Input:

```tsx
function Profile() {
return (
User profile picture
);
}
```

Output:

```tsx
import { useTranslation } from 'react-i18next';

function Profile() {
const { t } = useTranslation();
return (
{t('profile.user-profile-picture')}
);
}
```

### Template Literals with Variables

Input:

```tsx
function Greeting({ name, count }) {
return (

{`Welcome back, ${name}! You have ${count} notifications`}

);
}
```

Output:

```tsx
import { useTranslation } from 'react-i18next';

function Greeting({ name, count }) {
const { t } = useTranslation();
return (


{t('greeting.welcome-back-you-have-notifications', {
name,
count
})}

);
}
```

## Configuration

### Translatable Attributes

The following JSX attributes are processed for translation:

- `alt`
- `title`
- `placeholder`
- `aria-label`
- `aria-description`

You can modify the `JSX_ATTRIBUTES_TO_TRANSLATE` constant in the code to add more attributes.

### Blacklisted Template Literals

Some template literal attributes are blacklisted from translation:

- `className`
- `href`
- `src`
- `key`

### Translation Key Generation

Translation keys are automatically generated based on:

- Text content is slugified (converted to kebab-case)
- Special characters are removed
- Maximum length is 40 characters (configurable)
- Keys are prefixed with the component name in lowercase

## Best Practices

### 1. Review Generated Translations

Always review the generated translation keys and values for accuracy. The codemod makes intelligent guesses but might need adjustments.

### 2. Backup Your Code

Make sure to commit your changes before running the codemod:

```bash
git add .
git commit -m "pre-translation-codemod"
```

### 3. Run in Steps

For large codebases, consider running the transformation in smaller batches:

```bash
# Transform one component at a time
jscodeshift -t transform.ts src/components/Header.tsx \
--parser=tsx \
--translationFilePath=./translations.json \
--importName=react-i18next
```

### 4. Handle Special Cases

Some texts might need manual attention:

- Complex template literals with conditional expressions
- Dynamic content with function calls
- Special formatting requirements
- Pluralization cases

## Common Issues and Solutions

### 1. Long Translation Keys

Problem: Keys are too long or unclear
Solution: Adjust `TRANSLATION_KEY_MAX_LENGTH` in constants or manually rename keys

### 2. Missing Translations

Problem: Some text isn't being translated
Solution: Check if the text matches any `TRANSLATION_BLACKLIST` entries

### 3. Variable Names

Problem: Template literal variables have unclear names
Solution: Use meaningful variable names in your components

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT

## Credits

- Built with [jscodeshift](https://github.com/facebook/jscodeshift)
- [@Dschoordsch](https://github.com/Dschoordsch) for giving an idea [here](https://github.com/ParabolInc/parabol/pull/7155/files#diff-3301ada7ba726aadaa1866e63db8220359271fa6910dfee14e653ea83f7d839c)
- [ast-i18n](https://github.com/sibelius/ast-i18n) for showcasing other way of doing it

## Links

- [https://react.i18next.com/](https://react.i18next.com/)
- [https://next.i18next.com](https://next.i18next.com)