{"id":19894822,"url":"https://github.com/bartoszjarocki/jscodeshift-react-i18next","last_synced_at":"2025-05-02T20:30:46.067Z","repository":{"id":83155765,"uuid":"574092765","full_name":"BartoszJarocki/jscodeshift-react-i18next","owner":"BartoszJarocki","description":"jscodeshift transform that aims to extract hardocded strings in React applications","archived":false,"fork":false,"pushed_at":"2025-02-04T17:39:15.000Z","size":45,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T05:11:12.418Z","etag":null,"topics":["ast","codemod","i18next","jscodeshift"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BartoszJarocki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-04T12:08:33.000Z","updated_at":"2025-02-07T03:27:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"3a99778b-159b-46b7-bf98-85b235efad81","html_url":"https://github.com/BartoszJarocki/jscodeshift-react-i18next","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BartoszJarocki%2Fjscodeshift-react-i18next","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BartoszJarocki%2Fjscodeshift-react-i18next/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BartoszJarocki%2Fjscodeshift-react-i18next/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BartoszJarocki%2Fjscodeshift-react-i18next/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BartoszJarocki","download_url":"https://codeload.github.com/BartoszJarocki/jscodeshift-react-i18next/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252103960,"owners_count":21695392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","codemod","i18next","jscodeshift"],"created_at":"2024-11-12T18:34:46.099Z","updated_at":"2025-05-02T20:30:46.061Z","avatar_url":"https://github.com/BartoszJarocki.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React i18n Codemod\n\nA 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.\n\n## Features\n\n- ✨ Extracts text from JSX elements\n- 🔤 Handles translatable attributes (alt, title, aria-label, etc.)\n- 📝 Supports template literals with variables\n- 🔑 Automatically generates translation keys\n- 💾 Updates translation JSON files\n- 🔄 Adds necessary imports and hooks\n- 📦 TypeScript support\n\n## Installation\n\n```bash\nnpm install -g jscodeshift\nnpm install --save-dev @types/jscodeshift\n```\n\n## Usage\n\n```bash\nnpx jscodeshift -t transform.ts src/**/*.tsx --parser=tsx \\\n  --translationFilePath=./translations.json \\\n  --importName=react-i18next\n```\n\nbefore\n\n```tsx\nimport React from 'react';\n\ninterface User {\n  name: string;\n  role: string;\n  notifications: number;\n}\n\nconst user: User = {\n  name: 'John Doe',\n  role: 'Admin',\n  notifications: 5\n};\n\nconst formatDate = (date: Date) =\u003e date.toLocaleDateString();\nconst currentDate = new Date();\n\nexport const TestComponent: React.FC = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      {/* Simple text translation */}\n      \u003ch1\u003eWelcome to Our Platform\u003c/h1\u003e\n      \u003cp\u003ePlease read the instructions carefully\u003c/p\u003e\n\n      {/* Translatable attributes */}\n      \u003cimg \n        src=\"/profile.jpg\"\n        alt=\"User profile picture\" \n        title=\"Click to edit your profile picture\"\n      /\u003e\n      \n      \u003cinput\n        type=\"text\"\n        placeholder=\"Enter your username\"\n        title=\"Username must be at least 3 characters\"\n        aria-label=\"Username input field\"\n      /\u003e\n\n      {/* Template literals with variables */}\n      \u003cdiv aria-description=\"User information section\"\u003e\n        {`Hello ${user.name}, you are logged in as ${user.role}`}\n      \u003c/div\u003e\n\n      {/* Multiple variables and nested properties */}\n      \u003cdiv aria-label=\"Notification count\"\u003e\n        {`You have ${user.notifications} new notifications as of ${formatDate(currentDate)}`}\n      \u003c/div\u003e\n\n      {/* Mixed content */}\n      \u003csection\u003e\n        \u003ch2\u003eAccount Overview\u003c/h2\u003e\n        \u003cp\u003e{`Last login: ${formatDate(currentDate)}`}\u003c/p\u003e\n        \u003cdiv aria-description=\"Account status indicator\"\u003e\n          Account Status: Active\n        \u003c/div\u003e\n      \u003c/section\u003e\n\n      {/* Complex template literals */}\n      \u003cp aria-label=\"Subscription status\"\u003e\n        {`Your subscription ${user.notifications \u003e 0 ? 'requires' : 'does not require'} attention`}\n      \u003c/p\u003e\n\n      {/* Multiple elements with translatable content */}\n      \u003cfooter\u003e\n        \u003cp\u003eThank you for using our service\u003c/p\u003e\n        \u003ca \n          href=\"/support\"\n          title=\"Get help from our support team\"\n          aria-label=\"Contact support\"\n        \u003e\n          Need help? Contact support\n        \u003c/a\u003e\n        \u003cdiv\u003e{`© ${new Date().getFullYear()} All rights reserved`}\u003c/div\u003e\n      \u003c/footer\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nafter\n\n```tsx\nimport { useTranslation } from 'react-i18next';\nimport React from 'react';\n\ninterface User {\n  name: string;\n  role: string;\n  notifications: number;\n}\n\nconst user: User = {\n  name: 'John Doe',\n  role: 'Admin',\n  notifications: 5\n};\n\nconst formatDate = (date: Date) =\u003e date.toLocaleDateString();\nconst currentDate = new Date();\n\nexport const TestComponent: React.FC = () =\u003e {\n  const {\n    t\n  } = useTranslation();\n\n  return (\n    (\u003cdiv\u003e\n      {/* Simple text translation */}\n      \u003ch1\u003e{t('testComponent.welcome-to-our-platform')}\u003c/h1\u003e\n      \u003cp\u003e{t('testComponent.please-read-the-instructions-carefully')}\u003c/p\u003e\n      {/* Translatable attributes */}\n      \u003cimg \n        src=\"/profile.jpg\"\n        alt={t('testComponent.user-profile-picture')} \n        title={t('testComponent.click-to-edit-your-profile-picture')}\n      /\u003e\n      \u003cinput\n        type=\"text\"\n        placeholder=\"Enter your username\"\n        title={t('testComponent.username-must-be-at-least-3-characters')}\n        aria-label={t('testComponent.username-input-field')}\n      /\u003e\n      {/* Template literals with variables */}\n      \u003cdiv aria-description=\"User information section\"\u003e\n        {t('testComponent.hello-you-are-logged-in-as', {\n          name: user.name,\n          role: user.role\n        })}\n      \u003c/div\u003e\n      {/* Multiple variables and nested properties */}\n      \u003cdiv aria-label={t('testComponent.notification-count')}\u003e\n        {t('testComponent.you-have-new-notifications-as-of', {\n          notifications: user.notifications,\n          var2: formatDate(currentDate)\n        })}\n      \u003c/div\u003e\n      {/* Mixed content */}\n      \u003csection\u003e\n        \u003ch2\u003e{t('testComponent.account-overview')}\u003c/h2\u003e\n        \u003cp\u003e{t('testComponent.last-login', {\n          var1: formatDate(currentDate)\n        })}\u003c/p\u003e\n        \u003cdiv aria-description=\"Account status indicator\"\u003e{t('testComponent.account-status-active')}\u003c/div\u003e\n      \u003c/section\u003e\n      {/* Complex template literals */}\n      \u003cp aria-label={t('testComponent.subscription-status')}\u003e\n        {t('testComponent.your-subscription-attention', {\n          var1: user.notifications \u003e 0 ? 'requires' : 'does not require'\n        })}\n      \u003c/p\u003e\n      {/* Multiple elements with translatable content */}\n      \u003cfooter\u003e\n        \u003cp\u003e{t('testComponent.thank-you-for-using-our-service')}\u003c/p\u003e\n        \u003ca \n          href=\"/support\"\n          title={t('testComponent.get-help-from-our-support-team')}\n          aria-label={t('testComponent.contact-support')}\n        \u003e{t('testComponent.need-help-contact-support')}\u003c/a\u003e\n        \u003cdiv\u003e{t('testComponent.c-all-rights-reserved', {\n          var1: new Date().getFullYear()\n        })}\u003c/div\u003e\n      \u003c/footer\u003e\n    \u003c/div\u003e)\n  );\n};\n```\n\nand translation file created\n\n```json\n{\n  \"testComponent\": {\n    \"account-overview\": \"Account Overview\",\n    \"account-status-active\": \"Account Status: Active\",\n    \"c-all-rights-reserved\": \"© {{var1}} All rights reserved\",\n    \"click-to-edit-your-profile-picture\": \"Click to edit your profile picture\",\n    \"contact-support\": \"Contact support\",\n    \"get-help-from-our-support-team\": \"Get help from our support team\",\n    \"hello-you-are-logged-in-as\": \"Hello {{name}}, you are logged in as {{role}}\",\n    \"last-login\": \"Last login: {{var1}}\",\n    \"need-help-contact-support\": \"Need help? Contact support\",\n    \"notification-count\": \"Notification count\",\n    \"please-read-the-instructions-carefully\": \"Please read the instructions carefully\",\n    \"subscription-status\": \"Subscription status\",\n    \"thank-you-for-using-our-service\": \"Thank you for using our service\",\n    \"this-should-be-translated\": \"This SHOULD be translated\",\n    \"this-template-string-text-should-be-tran\": \"This template string text should be translated too, {{v1}}, and {{v2}} and that's it.\",\n    \"this-text-should-be-translated-too\": \"This text should be translated too\",\n    \"user-profile-picture\": \"User profile picture\",\n    \"username-input-field\": \"Username input field\",\n    \"username-must-be-at-least-3-characters\": \"Username must be at least 3 characters\",\n    \"welcome-to-our-platform\": \"Welcome to Our Platform\",\n    \"you-have-new-notifications-as-of\": \"You have {{notifications}} new notifications as of {{var2}}\",\n    \"your-subscription-attention\": \"Your subscription {{var1}} attention\"\n  }\n}\n```\n\n### Options\n\n- `translationFilePath` (required): Path to your translations JSON file\n- `translationRoot` (optional): Root key in translations file (e.g., 'en' or 'translations')\n- `importName` (required): Translation package to import (e.g., 'react-i18next', 'next-i18next')\n\n## Examples\n\n### Basic Text Translation\n\nInput:\n\n```tsx\nfunction Welcome() {\n  return \u003cdiv\u003eHello World\u003c/div\u003e;\n}\n```\n\nOutput:\n\n```tsx\nimport { useTranslation } from 'react-i18next';\n\nfunction Welcome() {\n  const { t } = useTranslation();\n  return \u003cdiv\u003e{t('welcome.hello-world')}\u003c/div\u003e;\n}\n```\n\n### Attribute Translation\n\nInput:\n\n```tsx\nfunction Profile() {\n  return (\n    \u003cimg \n      alt=\"User profile picture\" \n      title=\"Click to edit profile\"\n      aria-label=\"Profile image\"\n    /\u003e\n  );\n}\n```\n\nOutput:\n\n```tsx\nimport { useTranslation } from 'react-i18next';\n\nfunction Profile() {\n  const { t } = useTranslation();\n  return (\n    \u003cimg \n      alt={t('profile.user-profile-picture')}\n      title={t('profile.click-to-edit-profile')}\n      aria-label={t('profile.profile-image')}\n    /\u003e\n  );\n}\n```\n\n### Template Literals with Variables\n\nInput:\n\n```tsx\nfunction Greeting({ name, count }) {\n  return (\n    \u003cdiv\u003e{`Welcome back, ${name}! You have ${count} notifications`}\u003c/div\u003e\n  );\n}\n```\n\nOutput:\n\n```tsx\nimport { useTranslation } from 'react-i18next';\n\nfunction Greeting({ name, count }) {\n  const { t } = useTranslation();\n  return (\n    \u003cdiv\u003e\n      {t('greeting.welcome-back-you-have-notifications', { \n        name, \n        count \n      })}\n    \u003c/div\u003e\n  );\n}\n```\n\n## Configuration\n\n### Translatable Attributes\n\nThe following JSX attributes are processed for translation:\n\n- `alt`\n- `title`\n- `placeholder`\n- `aria-label`\n- `aria-description`\n\nYou can modify the `JSX_ATTRIBUTES_TO_TRANSLATE` constant in the code to add more attributes.\n\n### Blacklisted Template Literals\n\nSome template literal attributes are blacklisted from translation:\n\n- `className`\n- `href`\n- `src`\n- `key`\n\n### Translation Key Generation\n\nTranslation keys are automatically generated based on:\n\n- Text content is slugified (converted to kebab-case)\n- Special characters are removed\n- Maximum length is 40 characters (configurable)\n- Keys are prefixed with the component name in lowercase\n\n## Best Practices\n\n### 1. Review Generated Translations\n\nAlways review the generated translation keys and values for accuracy. The codemod makes intelligent guesses but might need adjustments.\n\n### 2. Backup Your Code\n\nMake sure to commit your changes before running the codemod:\n\n```bash\ngit add .\ngit commit -m \"pre-translation-codemod\"\n```\n\n### 3. Run in Steps\n\nFor large codebases, consider running the transformation in smaller batches:\n\n```bash\n# Transform one component at a time\njscodeshift -t transform.ts src/components/Header.tsx \\\n  --parser=tsx \\\n  --translationFilePath=./translations.json \\\n  --importName=react-i18next\n```\n\n### 4. Handle Special Cases\n\nSome texts might need manual attention:\n\n- Complex template literals with conditional expressions\n- Dynamic content with function calls\n- Special formatting requirements\n- Pluralization cases\n\n## Common Issues and Solutions\n\n### 1. Long Translation Keys\n\nProblem: Keys are too long or unclear\nSolution: Adjust `TRANSLATION_KEY_MAX_LENGTH` in constants or manually rename keys\n\n### 2. Missing Translations\n\nProblem: Some text isn't being translated\nSolution: Check if the text matches any `TRANSLATION_BLACKLIST` entries\n\n### 3. Variable Names\n\nProblem: Template literal variables have unclear names\nSolution: Use meaningful variable names in your components\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nMIT\n\n## Credits\n\n- Built with [jscodeshift](https://github.com/facebook/jscodeshift)\n- [@Dschoordsch](https://github.com/Dschoordsch) for giving an idea [here](https://github.com/ParabolInc/parabol/pull/7155/files#diff-3301ada7ba726aadaa1866e63db8220359271fa6910dfee14e653ea83f7d839c)\n- [ast-i18n](https://github.com/sibelius/ast-i18n) for showcasing other way of doing it\n\n## Links\n\n- [https://react.i18next.com/](https://react.i18next.com/)\n- [https://next.i18next.com](https://next.i18next.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartoszjarocki%2Fjscodeshift-react-i18next","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbartoszjarocki%2Fjscodeshift-react-i18next","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartoszjarocki%2Fjscodeshift-react-i18next/lists"}