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
- Host: GitHub
- URL: https://github.com/bartoszjarocki/jscodeshift-react-i18next
- Owner: BartoszJarocki
- License: mit
- Created: 2022-12-04T12:08:33.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-02-04T17:39:15.000Z (8 months ago)
- Last Synced: 2025-04-07T05:11:12.418Z (6 months ago)
- Topics: ast, codemod, i18next, jscodeshift
- Language: TypeScript
- Homepage:
- Size: 43.9 KB
- Stars: 8
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
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 */}
![]()
{/* 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 */}
![]()
{/* 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() {
returnHello 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 (
![]()
);
}
```Output:
```tsx
import { useTranslation } from 'react-i18next';function Profile() {
const { t } = useTranslation();
return (
![]()
);
}
```### 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)