├── .all-contributorsrc ├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── examples └── with-typescript │ ├── app │ ├── components │ │ ├── App │ │ │ ├── index.tsx │ │ │ └── messages.ts │ │ ├── Greeting │ │ │ ├── index.tsx │ │ │ └── messages.ts │ │ └── LanguageProvider │ │ │ └── index.tsx │ ├── index.d.ts │ ├── index.html │ ├── index.tsx │ └── translations │ │ ├── en.json │ │ └── ja.json │ ├── babel.config.js │ ├── package.json │ ├── readme.md │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock ├── jest.config.js ├── license ├── package.json ├── readme.md ├── renovate.json ├── src ├── __tests__ │ ├── __snapshots__ │ │ ├── components.test.ts.snap │ │ ├── hook.test.ts.snap │ │ ├── index.test.ts.snap │ │ └── injection.test.ts.snap │ ├── components.test.ts │ ├── hook.test.ts │ ├── index.test.ts │ └── injection.test.ts ├── babel-plugin-tester.d.ts ├── index.ts ├── types.ts ├── utils │ ├── getPrefix.ts │ ├── index.ts │ ├── isImportLocalName.ts │ └── testUtils.ts └── visitors │ ├── addIdToDefineMessage.ts │ ├── addIdToFormatMessage.ts │ └── jsx.ts ├── tsconfig.json ├── types.d.ts └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "babel-plugin-react-intl-auto", 3 | "projectOwner": "akameco", 4 | "files": [ 5 | "readme.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": true, 9 | "contributors": [ 10 | { 11 | "login": "akameco", 12 | "name": "akameco", 13 | "avatar_url": "https://avatars2.githubusercontent.com/u/4002137?v=4", 14 | "profile": "http://akameco.github.io", 15 | "contributions": [ 16 | "code", 17 | "test", 18 | "review", 19 | "doc" 20 | ] 21 | }, 22 | { 23 | "login": "Alxandr", 24 | "name": "Aleksander Heintz", 25 | "avatar_url": "https://avatars0.githubusercontent.com/u/112334?v=4", 26 | "profile": "http://alxandr.me", 27 | "contributions": [ 28 | "code", 29 | "doc" 30 | ] 31 | }, 32 | { 33 | "login": "mehcode", 34 | "name": "Ryan Leckey", 35 | "avatar_url": "https://avatars1.githubusercontent.com/u/753919?v=4", 36 | "profile": "https://github.com/mehcode", 37 | "contributions": [ 38 | "code" 39 | ] 40 | }, 41 | { 42 | "login": "adam-26", 43 | "name": "Adam", 44 | "avatar_url": "https://avatars1.githubusercontent.com/u/2652619?v=4", 45 | "profile": "https://github.com/adam-26", 46 | "contributions": [ 47 | "code", 48 | "doc" 49 | ] 50 | }, 51 | { 52 | "login": "Ephys", 53 | "name": "Guylian Cox", 54 | "avatar_url": "https://avatars0.githubusercontent.com/u/1280915?v=4", 55 | "profile": "https://ephys.github.io", 56 | "contributions": [ 57 | "code", 58 | "doc", 59 | "test" 60 | ] 61 | }, 62 | { 63 | "login": "carlgrundberg", 64 | "name": "Carl Grundberg", 65 | "avatar_url": "https://avatars1.githubusercontent.com/u/928407?v=4", 66 | "profile": "http://carlgrundberg.github.io/", 67 | "contributions": [ 68 | "example", 69 | "doc" 70 | ] 71 | }, 72 | { 73 | "login": "bradbarrow", 74 | "name": "bradbarrow", 75 | "avatar_url": "https://avatars3.githubusercontent.com/u/1264276?v=4", 76 | "profile": "http://bradbarrow.com", 77 | "contributions": [ 78 | "code", 79 | "doc", 80 | "test" 81 | ] 82 | }, 83 | { 84 | "login": "mgtitimoli", 85 | "name": "Mauro Gabriel Titimoli", 86 | "avatar_url": "https://avatars2.githubusercontent.com/u/4404683?v=4", 87 | "profile": "https://github.com/mgtitimoli", 88 | "contributions": [ 89 | "code", 90 | "test" 91 | ] 92 | }, 93 | { 94 | "login": "stanislav-ermakov", 95 | "name": "Stanislav Ermakov", 96 | "avatar_url": "https://avatars2.githubusercontent.com/u/15980086?v=4", 97 | "profile": "https://github.com/stanislav-ermakov", 98 | "contributions": [ 99 | "code" 100 | ] 101 | }, 102 | { 103 | "login": "chitoku-k", 104 | "name": "Chitoku", 105 | "avatar_url": "https://avatars1.githubusercontent.com/u/6535425?v=4", 106 | "profile": "https://chitoku.jp/", 107 | "contributions": [ 108 | "code" 109 | ] 110 | }, 111 | { 112 | "login": "kuma-kuma", 113 | "name": "Kouta Kumagai", 114 | "avatar_url": "https://avatars0.githubusercontent.com/u/12218082?v=4", 115 | "profile": "https://github.com/kuma-kuma", 116 | "contributions": [ 117 | "doc", 118 | "code", 119 | "test" 120 | ] 121 | }, 122 | { 123 | "login": "shahyar", 124 | "name": "Shahyar G", 125 | "avatar_url": "https://avatars0.githubusercontent.com/u/255846?v=4", 126 | "profile": "http://shah.yar.gs", 127 | "contributions": [ 128 | "code" 129 | ] 130 | }, 131 | { 132 | "login": "remcohaszing", 133 | "name": "Remco Haszing", 134 | "avatar_url": "https://avatars2.githubusercontent.com/u/779047?v=4", 135 | "profile": "https://gitlab.com/remcohaszing", 136 | "contributions": [ 137 | "code" 138 | ] 139 | }, 140 | { 141 | "login": "jmarceli", 142 | "name": "jmarceli", 143 | "avatar_url": "https://avatars1.githubusercontent.com/u/4281333?v=4", 144 | "profile": "https://github.com/jmarceli", 145 | "contributions": [ 146 | "code", 147 | "test" 148 | ] 149 | }, 150 | { 151 | "login": "dominik-zeglen", 152 | "name": "Dominik Żegleń", 153 | "avatar_url": "https://avatars3.githubusercontent.com/u/6833443?v=4", 154 | "profile": "https://github.com/dominik-zeglen", 155 | "contributions": [ 156 | "code", 157 | "test" 158 | ] 159 | }, 160 | { 161 | "login": "Filson14", 162 | "name": "Filip \"Filson\" Pasternak", 163 | "avatar_url": "https://avatars1.githubusercontent.com/u/4540538?v=4", 164 | "profile": "https://github.com/Filson14", 165 | "contributions": [ 166 | "code" 167 | ] 168 | }, 169 | { 170 | "login": "ericmasiello", 171 | "name": "Eric Masiello", 172 | "avatar_url": "https://avatars3.githubusercontent.com/u/3525886?v=4", 173 | "profile": "https://github.com/ericmasiello", 174 | "contributions": [ 175 | "code", 176 | "test" 177 | ] 178 | } 179 | ], 180 | "repoType": "github", 181 | "commitConvention": "none" 182 | } 183 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "targets": { "node": "8" } }], 4 | "@babel/preset-typescript" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: xgkdsD7kShO8c0G1f51R77L0oshots57p 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | examples 3 | jest.config.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "precure/auto", 3 | "rules": { 4 | "unicorn/prevent-abbreviations": "off", 5 | "@typescript-eslint/explicit-function-return-type": "off", 6 | "@typescript-eslint/no-explicit-any": "off", 7 | "jest/require-tothrow-message": "off", 8 | "jest/no-empty-title": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | flowtyped/npm binary 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | * version: 10 | * `node` version: 11 | * `npm` (or `yarn`) version: 12 | 13 | **Do you want to request a _feature_ or report a _bug_?:** 14 | 15 | **What is the current behavior?:** 16 | 17 | **What is the expected behavior?:** 18 | 19 | **Suggested solution:** 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | **What**: 8 | 9 | 10 | 11 | **Why**: 12 | 13 | 14 | 15 | **How**: 16 | 17 | 18 | **Checklist**: 19 | 20 | * [ ] Documentation 21 | * [ ] Tests 22 | * [ ] Ready to be merged 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | platform: [ubuntu-latest, windows-latest] 10 | node-version: [12.x, 13.x] 11 | runs-on: ${{ matrix.platform }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node }} 17 | - run: yarn install 18 | - run: yarn run fmt --check 19 | - run: yarn run tsc --noEmit 20 | - run: yarn run test:ci 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dist 4 | coverage 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/flow-typed/** 2 | coverage 3 | lib 4 | dist 5 | package.json 6 | .github 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at akameco.t@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /examples/with-typescript/app/components/App/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { FormattedMessage, useIntl } from 'react-intl' 3 | import Greeting from '../Greeting' 4 | import messages from './messages' 5 | 6 | export default function App() { 7 | const intl = useIntl() 8 | const user = { 9 | name: 'Eric', 10 | unreadCount: 4, 11 | lastLoginTime: Date.now() - 1000 * 60 * 60 * 24, 12 | } 13 | 14 | return ( 15 |
16 | 17 | {intl.formatMessage({ defaultMessage: 'world' })} 18 | 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /examples/with-typescript/app/components/App/messages.ts: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl' 2 | 3 | export default defineMessages({ 4 | hello: 'hello', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/with-typescript/app/components/Greeting/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { 3 | FormattedMessage, 4 | FormattedNumber, 5 | FormattedRelativeTime, 6 | } from 'react-intl' 7 | import messages from './messages' 8 | 9 | interface User { 10 | name: string 11 | unreadCount: number 12 | lastLoginTime: number 13 | } 14 | 15 | interface UserProps { 16 | user: User 17 | } 18 | 19 | export default function Greeting({ user }: UserProps) { 20 | return ( 21 |

22 | {user.name}, 26 | unreadCount: user.unreadCount, 27 | formattedUnreadCount: ( 28 | 29 | 30 | 31 | ), 32 | formattedLastLoginTime: ( 33 | 34 | ), 35 | }} 36 | /> 37 |

38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /examples/with-typescript/app/components/Greeting/messages.ts: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl' 2 | 3 | export default defineMessages({ 4 | welcome: `Welcome {name}, you have received {unreadCount, plural, =0 {no new messages} one {{formattedUnreadCount} new message} other {{formattedUnreadCount} new messages}} since {formattedLastLoginTime}.`, 5 | }) 6 | -------------------------------------------------------------------------------- /examples/with-typescript/app/components/LanguageProvider/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { IntlProvider } from 'react-intl' 3 | 4 | import enMessages from '../../translations/en.json' 5 | import jaMessages from '../../translations/ja.json' 6 | 7 | const messages: { 8 | [key: string]: {} 9 | } = { 10 | en: enMessages, 11 | ja: jaMessages, 12 | } 13 | 14 | export default function LanguageProvider({ 15 | children, 16 | }: { 17 | children: React.ReactNode 18 | }) { 19 | const [locale, setLocale] = React.useState('en') 20 | 21 | return ( 22 |
23 | 24 | {children} 25 | 26 | setLocale('en')}>English/ 27 | setLocale('ja')}>日本語 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-typescript/app/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' { 2 | interface Translation { 3 | [key: string]: {} 4 | } 5 | 6 | const Translation: Translation 7 | export default Translation 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-typescript/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-intl-auto example 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/with-typescript/app/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import App from './components/App' 4 | import LanguageProvider from './components/LanguageProvider' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /examples/with-typescript/app/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "components.App.4220927227": "world", 3 | "components.App.hello": "hello", 4 | "components.Greeting.welcome": "Welcome {name}, you have received {unreadCount, plural, =0 {no new messages} one {{formattedUnreadCount} new message} other {{formattedUnreadCount} new messages}} since {formattedLastLoginTime}." 5 | } 6 | -------------------------------------------------------------------------------- /examples/with-typescript/app/translations/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "components.App.4220927227": "世界", 3 | "components.App.hello": "こんにちは", 4 | "components.Greeting.welcome": "ようこそ {name}, {formattedLastLoginTime}から {unreadCount, plural, =0 {メッセージはありません} other {{formattedUnreadCount} 件のメッセージがあります}}" 5 | } 6 | -------------------------------------------------------------------------------- /examples/with-typescript/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | 4 | return { 5 | presets: ['@babel/preset-react', '@babel/preset-typescript'], 6 | plugins: [ 7 | [ 8 | 'react-intl-auto', 9 | { 10 | removePrefix: 'app/', 11 | }, 12 | ], 13 | ], 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/with-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-react-app-example-typescript", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "webpack-dev-server --hot", 6 | "build": "webpack", 7 | "i18n": "extract-react-intl-messages -l=en,ja -o app/translations -d en --flat 'app/**/!(*.test).{ts,tsx}'" 8 | }, 9 | "dependencies": { 10 | "@babel/preset-typescript": "^7.9.0", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-intl": "^4.5.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/cli": "^7.8.4", 17 | "@babel/core": "^7.9.0", 18 | "@babel/plugin-transform-typescript": "^7.9.4", 19 | "@babel/preset-env": "^7.9.5", 20 | "@babel/preset-react": "^7.9.4", 21 | "@types/react": "^16.9.34", 22 | "@types/react-dom": "^16.9.6", 23 | "@types/react-intl": "^3.0.0", 24 | "babel-loader": "~8.1.0", 25 | "babel-plugin-react-intl": "^7.5.2", 26 | "babel-plugin-react-intl-auto": "3.3.0", 27 | "extract-react-intl": "^0.8.1", 28 | "extract-react-intl-messages": "^4.1.1", 29 | "ts-loader": "^7.0.1", 30 | "typescript": "^3.8.3", 31 | "webpack": "^4.43.0", 32 | "webpack-cli": "^3.3.11", 33 | "webpack-dev-server": "^3.10.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/with-typescript/readme.md: -------------------------------------------------------------------------------- 1 | # Example App 2 | 3 | ## Install 4 | 5 | ``` 6 | $ yarn 7 | $ npm link babel-plugin-react-intl-auto 8 | ``` 9 | 10 | ## Run 11 | 12 | ``` 13 | $ yarn start 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/with-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "strict": true 10 | }, 11 | "include": [ 12 | "app/**/*.ts", 13 | "node_modules/babel-plugin-react-intl-auto/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/with-typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | devtool: 'inline-source-map', 7 | context: path.resolve(__dirname, './app'), 8 | entry: './index.tsx', 9 | output: { 10 | filename: 'bundle.js', 11 | path: path.resolve(__dirname, './dist'), 12 | publicPath: '/assets', 13 | }, 14 | devServer: { 15 | contentBase: path.resolve(__dirname, './app'), 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.tsx', '.js'], 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | exclude: [/node_modules/], 25 | use: [ 26 | { 27 | loader: 'babel-loader', 28 | }, 29 | ], 30 | }, 31 | { 32 | test: /\.tsx?$/, 33 | exclude: [/node_modules/], 34 | use: [ 35 | { 36 | loader: 'babel-loader', 37 | }, 38 | { 39 | loader: 'ts-loader', 40 | }, 41 | ], 42 | }, 43 | ], 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | snapshotSerializers: [ 4 | require.resolve('string-snapshot-serializer/serializer'), 5 | ], 6 | modulePathIgnorePatterns: ['/lib'], 7 | } 8 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) akameco (akameco.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-react-intl-auto", 3 | "version": "3.3.0", 4 | "main": "lib/index.js", 5 | "types": "types.d.ts", 6 | "description": "i18n for the component age. Auto management react-intl ID", 7 | "license": "MIT", 8 | "repository": "akameco/babel-plugin-react-intl-auto", 9 | "author": { 10 | "name": "akameco", 11 | "email": "akameco.t@gmail.com", 12 | "url": "https://akameco.github.io" 13 | }, 14 | "engines": { 15 | "node": ">=10" 16 | }, 17 | "scripts": { 18 | "build": "babel src -d lib --ignore __tests__,__fixtures__ --extensions .ts", 19 | "prepack": "yarn build", 20 | "fmt": "prettier --write .", 21 | "lint": "eslint src --ext ts", 22 | "add:coveralls": "cat ./coverage/lcov.info | coveralls", 23 | "test": "jest", 24 | "test:watch": "jest --watch", 25 | "test:coverage": "jest --coverage --ci --runInBand", 26 | "test:ci": "yarn lint && yarn test:coverage" 27 | }, 28 | "lint-staged": { 29 | "*.{ts}": [ 30 | "prettier --write", 31 | "eslint" 32 | ], 33 | "*.{js,json,md}": [ 34 | "prettier --write" 35 | ] 36 | }, 37 | "keywords": [ 38 | "react", 39 | "react-components", 40 | "react-intl", 41 | "i18n", 42 | "react-intl-auto", 43 | "babel-plugin", 44 | "auto", 45 | "babel", 46 | "plugin", 47 | "generate", 48 | "defineMessages" 49 | ], 50 | "files": [ 51 | "lib", 52 | "types.d.ts" 53 | ], 54 | "dependencies": { 55 | "@babel/core": "^7.9.0", 56 | "@babel/traverse": "^7.9.0", 57 | "@babel/types": "^7.9.0", 58 | "murmurhash3js": "^3.0.1" 59 | }, 60 | "devDependencies": { 61 | "@akameco/tsconfig": "0.4.0", 62 | "@babel/cli": "7.14.8", 63 | "@babel/preset-env": "7.14.8", 64 | "@babel/preset-typescript": "7.14.5", 65 | "@babel/register": "7.14.5", 66 | "@types/babel__core": "7.1.15", 67 | "@types/babel__traverse": "7.14.2", 68 | "@types/jest": "26.0.24", 69 | "@types/murmurhash3js": "3.0.2", 70 | "@types/node": "14.17.5", 71 | "babel-core": "7.0.0-bridge.0", 72 | "babel-eslint": "10.1.0", 73 | "babel-jest": "26.6.3", 74 | "babel-log": "2.0.0", 75 | "babel-plugin-tester": "9.2.0", 76 | "coveralls": "3.1.1", 77 | "eslint": "7.31.0", 78 | "eslint-config-precure": "5.4.0", 79 | "husky": "4.3.8", 80 | "jest": "26.6.3", 81 | "lint-staged": "10.5.4", 82 | "prettier": "2.3.2", 83 | "react-intl": "4.7.6", 84 | "string-snapshot-serializer": "1.0.1", 85 | "typescript": "3.9.10" 86 | }, 87 | "husky": { 88 | "hooks": { 89 | "pre-commit": "lint-staged" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-react-intl-auto 2 | 3 | [![test](https://github.com/akameco/babel-plugin-react-intl-auto/workflows/test/badge.svg)](https://github.com/akameco/babel-plugin-react-intl-auto/actions?query=workflow%3Atest) 4 | [![Coverage Status](https://coveralls.io/repos/github/akameco/babel-plugin-react-intl-auto/badge.svg?branch=master)](https://coveralls.io/github/akameco/babel-plugin-react-intl-auto?branch=master) 5 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 6 | [![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest) 7 | [![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) 8 | [![babel-plugin-react-intl-auto Dev Token](https://badge.devtoken.rocks/babel-plugin-react-intl-auto)](https://devtoken.rocks/package/babel-plugin-react-intl-auto) 9 | 10 | > i18n for the component age. Auto management react-intl ID. 11 | 12 | [React Intl](https://github.com/formatjs/react-intl) is awesome. But, Global ID management is difficult and confusing. 13 | 14 | Many projects, like [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate), give the ID to the name of the component as a prefix. 15 | But it is redundant and troublesome. 16 | 17 | This babel-plugin releases you from cumbersome ID management. 18 | Based on the file path, this automatically generates a prefixed id. 19 | 20 | Also, we strongly encourage you to use [extract-react-intl-messages](https://github.com/akameco/extract-react-intl-messages). 21 | You can generate json automatically. 22 | 23 | Goodbye, global ID!! 24 | 25 | #### Before 26 | 27 | ```js 28 | import { defineMessages, FormattedMessage } from 'react-intl' 29 | 30 | export default defineMessages({ 31 | hello: { 32 | id: 'App.Components.Greeting.hello', 33 | defaultMessage: 'hello {name}', 34 | }, 35 | welcome: { 36 | id: 'App.Components.Greeting.welcome', 37 | defaultMessage: 'Welcome!', 38 | }, 39 | }) 40 | 41 | const MyComponent = () => ( 42 | 46 | ) 47 | ``` 48 | 49 | #### After 50 | 51 | With babel-plugin-react-intl-auto. 52 | 53 | ```js 54 | import { defineMessages, FormattedMessage } from 'react-intl' 55 | 56 | export default defineMessages({ 57 | hello: 'hello {name}', 58 | welcome: 'Welcome!', 59 | }) 60 | 61 | const MyComponent = () => 62 | ``` 63 | 64 | See [examples](https://github.com/akameco/babel-plugin-react-intl-auto/tree/master/examples). 65 | 66 | ### With `extract-react-intl-messages` 67 | 68 | Example usage with [extract-react-intl-messages](https://github.com/akameco/extract-react-intl-messages). 69 | 70 | ``` 71 | $ extract-messages -l=en -o translations 'src/**/*.js' 72 | ``` 73 | 74 | en.json 75 | 76 | ```json 77 | { 78 | "components.App.hello": "hello {name}", 79 | "components.App.welcome": "Welcome", 80 | "components.App.189751785": "goodbye {name}" // unique hash of defaultMessage 81 | } 82 | ``` 83 | 84 | ## Install 85 | 86 | npm 87 | 88 | ```shell 89 | $ npm install --save-dev babel-plugin-react-intl-auto 90 | 91 | # Optional: TypeScript support 92 | $ npm install --save-dev @babel/plugin-transform-typescript 93 | ``` 94 | 95 | yarn 96 | 97 | ```shell 98 | $ yarn add --dev babel-plugin-react-intl-auto 99 | 100 | # Optional: TypeScript support 101 | $ yarn add --dev @babel/plugin-transform-typescript 102 | ``` 103 | 104 | ## Usage 105 | 106 | .babelrc 107 | 108 | ```json 109 | { 110 | "plugins": [ 111 | [ 112 | "react-intl-auto", 113 | { 114 | "removePrefix": "app/", 115 | "filebase": false 116 | } 117 | ] 118 | ] 119 | } 120 | ``` 121 | 122 | ### with injectIntl 123 | 124 | Input: 125 | 126 | ```js 127 | import { injectIntl } from 'react-intl' 128 | 129 | const MyComponent = ({ intl }) => { 130 | const label = intl.formatMessage({ defaultMessage: 'Submit button' }) 131 | return 132 | } 133 | 134 | injectIntl(MyComponent) 135 | ``` 136 | 137 | ↓   ↓   ↓ 138 | 139 | Output: 140 | 141 | ```js 142 | import { injectIntl } from 'react-intl' 143 | 144 | const MyComponent = ({ intl }) => { 145 | const label = intl.formatMessage({ 146 | id: 'App.Components.Button.label', 147 | defaultMessage: 'Submit button', 148 | }) 149 | return 150 | } 151 | 152 | injectIntl(MyComponent) 153 | ``` 154 | 155 | ### with useIntl 156 | 157 | Input: 158 | 159 | ```js 160 | import { useIntl } from 'react-intl' 161 | 162 | const MyComponent = () => { 163 | const intl = useIntl() 164 | const label = intl.formatMessage({ defaultMessage: 'Submit button' }) 165 | return 166 | } 167 | ``` 168 | 169 | ↓   ↓   ↓ 170 | 171 | Output: 172 | 173 | ```js 174 | import { useIntl } from 'react-intl' 175 | 176 | const MyComponent = () => { 177 | const intl = useIntl() 178 | const label = intl.formatMessage({ 179 | id: 'App.Components.Button.label', 180 | defaultMessage: 'Submit button', 181 | }) 182 | return 183 | } 184 | ``` 185 | 186 | ### Options 187 | 188 | #### removePrefix 189 | 190 | remove prefix. 191 | 192 | Type: `string | boolean` | `regexp`
193 | Default: `''` 194 | 195 | if `removePrefix` is `true`, no file path prefix is included in the id. 196 | 197 | ##### Example (src/components/App/messages.js) 198 | 199 | when `removePrefix` is `"src"` 200 | 201 | ```js 202 | import { defineMessages } from 'react-intl'; 203 | 204 | export default defineMessages({ 205 | hello: 'hello world' 206 | }); 207 | 208 | ↓ ↓ ↓ ↓ ↓ ↓ 209 | 210 | import { defineMessages } from 'react-intl'; 211 | 212 | export default defineMessages({ 213 | hello: { 214 |    id: 'components.App.hello', 215 | defaultMessage: 'hello world' 216 | } 217 | }); 218 | ``` 219 | 220 | when `removePrefix` is `"src.components"` 221 | 222 | ```js 223 | import { defineMessages } from 'react-intl'; 224 | 225 | export default defineMessages({ 226 | hello: 'hello world' 227 | }); 228 | 229 | ↓ ↓ ↓ ↓ ↓ ↓ 230 | 231 | import { defineMessages } from 'react-intl'; 232 | 233 | export default defineMessages({ 234 | hello: { 235 |    id: 'App.hello', 236 | defaultMessage: 'hello world' 237 | } 238 | }); 239 | ``` 240 | 241 | when `removePrefix` is `true` 242 | 243 | ```js 244 | import { defineMessages } from 'react-intl'; 245 | 246 | export default defineMessages({ 247 | hello: 'hello world' 248 | }); 249 | 250 | ↓ ↓ ↓ ↓ ↓ ↓ 251 | 252 | import { defineMessages } from 'react-intl'; 253 | 254 | export default defineMessages({ 255 | hello: { 256 | id: 'hello', 257 | defaultMessage: 'hello world' 258 | } 259 | }); 260 | ``` 261 | 262 | #### filebase 263 | 264 | Type: `boolean`
265 | Default: `false` 266 | 267 | if `filebase` is `true`, generate id with filename. 268 | 269 | #### moduleSourceName 270 | 271 | Type: `string`
272 | Default: `react-intl` 273 | 274 | if set, enables to use custom module as a source for _defineMessages_ etc. 275 | 276 | https://github.com/akameco/babel-plugin-react-intl-auto/issues/74#issuecomment-528562743 277 | 278 | #### includeExportName 279 | 280 | Type: `boolean | 'all'`
281 | Default: `false` 282 | 283 | if `includeExportName` is `true`, adds named exports as part of the id. 284 | 285 | Only works with `defineMessages`. 286 | 287 | ##### Example 288 | 289 | ```js 290 | export const test = defineMessages({ 291 | hello: 'hello {name}', 292 | }) 293 | 294 | ↓ ↓ ↓ ↓ ↓ ↓ 295 | 296 | export const test = defineMessages({ 297 | hello: { 298 | id: 'path.to.file.test.hello', 299 | defaultMessage: 'hello {name}', 300 | }, 301 | }) 302 | ``` 303 | 304 | If includeExportName is `'all'`, it will also add `default` to the id on default 305 | exports. 306 | 307 | #### extractComments 308 | 309 | Use leading comments as the message description. 310 | 311 | Only works with `defineMessages` 312 | 313 | Type: `boolean`
314 | Default: `true` 315 | 316 | ##### Example 317 | 318 | ```js 319 | export const test = defineMessages({ 320 | // Message used to greet the user 321 | hello: 'hello {name}', 322 | }) 323 | 324 | ↓ ↓ ↓ ↓ ↓ ↓ 325 | 326 | export const test = defineMessages({ 327 | hello: { 328 | id: 'path.to.file.test.hello', 329 | defaultMessage: 'hello {name}', 330 | description: 'Message used to greet the user', 331 | }, 332 | }) 333 | ``` 334 | 335 | #### useKey 336 | 337 | Only works with `intl.formatMessage`, `FormattedMessage` and `FormattedHTMLMessage`. Instead of 338 | generating an ID by hashing `defaultMessage`, it will use the `key` property if 339 | it exists. 340 | 341 | Type: `boolean`
342 | Default: `false` 343 | 344 | ##### Example 345 | 346 | ```js 347 | intl.formatMessage({ 348 | key: 'foobar', 349 | defaultMessage: 'hello' 350 | }); 351 | 352 | ↓ ↓ ↓ ↓ ↓ ↓ 353 | 354 | intl.formatMessage({ 355 | key: 'foobar', 356 | defaultMessage: 'hello', 357 | "id": "path.to.file.foobar" 358 | }); 359 | ``` 360 | 361 | ```js 362 | 363 | 364 | ↓ ↓ ↓ ↓ ↓ ↓ 365 | 366 | 367 | ``` 368 | 369 | #### separator 370 | 371 | Allows you to specify a custom separator 372 | 373 | Type: `string`
374 | Default: `.` 375 | 376 | ##### Example 377 | 378 | when `separator` is `"_"` 379 | 380 | ```js 381 | export const test = defineMessages({ 382 | hello: 'hello {name}', 383 | }) 384 | 385 | ↓ ↓ ↓ ↓ ↓ ↓ 386 | 387 | export const test = defineMessages({ 388 | hello: { 389 | id: 'path_to_file_test_hello', 390 | defaultMessage: 'hello {name}', 391 | }, 392 | }) 393 | ``` 394 | 395 | #### relativeTo 396 | 397 | Allows you to specify the directory that is used when determining a file's prefix. 398 | 399 | This option is useful for monorepo setups. 400 | 401 | Type: `string`
402 | Default: `process.cwd()` 403 | 404 | ##### Example 405 | 406 | Folder structure with two sibling packages. `packageB` contains babel config and depends on `packageA`. 407 | 408 | ```bash 409 | |- packageA 410 | | | 411 | | -- componentA 412 | | 413 | |- packageB 414 | | | 415 | | -- componentB 416 | | | 417 | | -- .babelrc 418 | ``` 419 | 420 | Set `relativeTo` to parent directory in `packageB` babel config 421 | 422 | ```js 423 | { 424 | "plugins": [ 425 | [ 426 | "react-intl-auto", 427 | { 428 | "relativeTo": "..", 429 | // ... 430 | }, 431 | ], 432 | ] 433 | } 434 | ``` 435 | 436 | Run babel in packageB 437 | 438 | ```bash 439 | cd packageB && babel 440 | ``` 441 | 442 | Messages in `componentA` are prefixed relative to the project root 443 | 444 | ```js 445 | export const test = defineMessages({ 446 | hello: 'hello {name}', 447 | }) 448 | 449 | ↓ ↓ ↓ ↓ ↓ ↓ 450 | 451 | export const test = defineMessages({ 452 | hello: { 453 | id: 'packageA.componentA.hello', 454 | defaultMessage: 'hello {name}', 455 | }, 456 | }) 457 | ``` 458 | 459 | ### Support variable 460 | 461 | ##### Example 462 | 463 | ```js 464 | const messages = { hello: 'hello world' } 465 | 466 | export default defineMessages(messages) 467 | 468 | ↓ ↓ ↓ ↓ ↓ ↓ 469 | 470 | const messages = { 471 | hello: { 472 | id: 'path.to.file.hello', 473 | defaultMessage: 'hello wolrd' 474 | } 475 | }; 476 | 477 | export default defineMessages(messages); 478 | ``` 479 | 480 | ## TypeScript 481 | 482 | TypeScript support is bundled with this package. Be sure to include our type 483 | definition and run `@babel/plugin-transform-typescript` beforehand. This way, 484 | you can also be empowered by [extract-react-intl-messages](https://github.com/akameco/extract-react-intl-messages). 485 | 486 | ### tsconfig.json 487 | 488 | ```json 489 | { 490 | "compilerOptions": { 491 | // ... 492 | "jsx": "preserve" 493 | // ... 494 | }, 495 | "include": ["node_modules/babel-plugin-react-intl-auto/**/*.d.ts"] 496 | } 497 | ``` 498 | 499 | ### .babelrc 500 | 501 | ```json 502 | { 503 | "plugins": [["@babel/plugin-transform-typescript"], ["react-intl-auto"]] 504 | } 505 | ``` 506 | 507 | ### webpack.config.js 508 | 509 | Use `babel-loader` along with `ts-loader` when using webpack as well. 510 | 511 | ```js 512 | module.exports = { 513 | module: { 514 | rules: [ 515 | { 516 | test: /\.tsx?$/, 517 | exclude: [/node_modules/], 518 | use: [ 519 | { 520 | loader: 'babel-loader', 521 | }, 522 | { 523 | loader: 'ts-loader', 524 | }, 525 | ], 526 | }, 527 | ], 528 | }, 529 | } 530 | ``` 531 | 532 | ## Related 533 | 534 | ### [babel-plugin-react-intl-id-hash](https://github.com/adam-26/babel-plugin-react-intl-id-hash) 535 | 536 | If you want short consistent hash values for the ID, you can use [react-intl-id-hash](https://github.com/adam-26/babel-plugin-react-intl-id-hash) in addition to this plugin to help reduce your applications bundle size. 537 | 538 | ### [extract-react-intl-messages](https://github.com/akameco/extract-react-intl-messages) 539 | 540 | Extract react-intl messages. 541 | 542 | ## Contributors 543 | 544 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 |

akameco

💻 ⚠️ 👀 📖

Aleksander Heintz

💻 📖

Ryan Leckey

💻

Adam

💻 📖

Guylian Cox

💻 📖 ⚠️

Carl Grundberg

💡 📖

bradbarrow

💻 📖 ⚠️

Mauro Gabriel Titimoli

💻 ⚠️

Stanislav Ermakov

💻

Chitoku

💻

Kouta Kumagai

📖 💻 ⚠️

Shahyar G

💻

Remco Haszing

💻

jmarceli

💻 ⚠️
Dominik Żegleń
Dominik Żegleń

💻 ⚠️
Filip
Filip "Filson" Pasternak

💻
Eric Masiello
Eric Masiello

💻 ⚠️
Josh Poole
Josh Poole

💻 ⚠️
575 | 576 | 577 | 578 | 579 | 580 | 581 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! 582 | 583 | ## License 584 | 585 | MIT © [akameco](http://akameco.github.io) 586 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@akameco"] 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/components.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`default default: default 1`] = ` 4 | 5 | import { FormattedMessage } from 'react-intl'; 6 | 7 | ; 8 | 9 | ↓ ↓ ↓ ↓ ↓ ↓ 10 | 11 | import { FormattedMessage } from 'react-intl'; 12 | ; 13 | 14 | `; 15 | 16 | exports[`default import all supported components: import all supported components 1`] = ` 17 | 18 | import { FormattedHTMLMessage, FormattedMessage } from 'react-intl'; 19 | 20 | ; 21 | ; 22 | 23 | ↓ ↓ ↓ ↓ ↓ ↓ 24 | 25 | import { FormattedHTMLMessage, FormattedMessage } from 'react-intl'; 26 | ; 27 | ; 28 | 29 | `; 30 | 31 | exports[`default multiple uses: multiple uses 1`] = ` 32 | 33 | import { FormattedMessage } from 'react-intl'; 34 | 35 | ; 36 | ; 37 | 38 | ↓ ↓ ↓ ↓ ↓ ↓ 39 | 40 | import { FormattedMessage } from 'react-intl'; 41 | ; 42 | ; 43 | 44 | `; 45 | 46 | exports[`default using key: using key 1`] = ` 47 | 48 | import { FormattedMessage } from 'react-intl'; 49 | 50 | ; 51 | 52 | ↓ ↓ ↓ ↓ ↓ ↓ 53 | 54 | import { FormattedMessage } from 'react-intl'; 55 | ; 56 | 57 | `; 58 | 59 | exports[`default with FormattedMessage imported as something else: with FormattedMessage imported as something else 1`] = ` 60 | 61 | import { FormattedMessage as T } from 'react-intl'; 62 | 63 | ; 64 | 65 | ↓ ↓ ↓ ↓ ↓ ↓ 66 | 67 | import { FormattedMessage as T } from 'react-intl'; 68 | ; 69 | 70 | `; 71 | 72 | exports[`default with FormattedMessage nested in other JSX: with FormattedMessage nested in other JSX 1`] = ` 73 | 74 | import { FormattedMessage } from 'react-intl'; 75 | 76 |
77 | 78 |
79 | 80 | ↓ ↓ ↓ ↓ ↓ ↓ 81 | 82 | import { FormattedMessage } from 'react-intl'; 83 |
84 | 85 |
; 86 | 87 | `; 88 | 89 | exports[`default with a value interpolated in the message: with a value interpolated in the message 1`] = ` 90 | 91 | import { FormattedMessage } from 'react-intl'; 92 | 93 | ; 94 | 95 | ↓ ↓ ↓ ↓ ↓ ↓ 96 | 97 | import { FormattedMessage } from 'react-intl'; 98 | ; 99 | 100 | `; 101 | 102 | exports[`default with a variable as the defaultMessage: with a variable as the defaultMessage 1`] = ` 103 | 104 | import { FormattedMessage } from 'react-intl'; 105 | 106 | const message = "variable message"; 107 | 108 | ; 109 | 110 | ↓ ↓ ↓ ↓ ↓ ↓ 111 | 112 | import { FormattedMessage } from 'react-intl'; 113 | const message = "variable message"; 114 | ; 115 | 116 | `; 117 | 118 | exports[`extractComments = false default: default 1`] = ` 119 | 120 | import { FormattedMessage } from 'react-intl'; 121 | 122 | ; 123 | 124 | ↓ ↓ ↓ ↓ ↓ ↓ 125 | 126 | import { FormattedMessage } from 'react-intl'; 127 | ; 128 | 129 | `; 130 | 131 | exports[`filebase = true default: default 1`] = ` 132 | 133 | import { FormattedMessage } from 'react-intl'; 134 | 135 | ; 136 | 137 | ↓ ↓ ↓ ↓ ↓ ↓ 138 | 139 | import { FormattedMessage } from 'react-intl'; 140 | ; 141 | 142 | `; 143 | 144 | exports[`includeExportName = all default: default 1`] = ` 145 | 146 | import { FormattedMessage } from 'react-intl'; 147 | 148 | ; 149 | 150 | ↓ ↓ ↓ ↓ ↓ ↓ 151 | 152 | import { FormattedMessage } from 'react-intl'; 153 | ; 154 | 155 | `; 156 | 157 | exports[`includeExportName = true default: default 1`] = ` 158 | 159 | import { FormattedMessage } from 'react-intl'; 160 | 161 | ; 162 | 163 | ↓ ↓ ↓ ↓ ↓ ↓ 164 | 165 | import { FormattedMessage } from 'react-intl'; 166 | ; 167 | 168 | `; 169 | 170 | exports[`removePrefix = "src" default: default 1`] = ` 171 | 172 | import { FormattedMessage } from 'react-intl'; 173 | 174 | ; 175 | 176 | ↓ ↓ ↓ ↓ ↓ ↓ 177 | 178 | import { FormattedMessage } from 'react-intl'; 179 | ; 180 | 181 | `; 182 | 183 | exports[`removePrefix = "src.__fixtures__" default: default 1`] = ` 184 | 185 | import { FormattedMessage } from 'react-intl'; 186 | 187 | ; 188 | 189 | ↓ ↓ ↓ ↓ ↓ ↓ 190 | 191 | import { FormattedMessage } from 'react-intl'; 192 | ; 193 | 194 | `; 195 | 196 | exports[`removePrefix = "src/" -- with slash default: default 1`] = ` 197 | 198 | import { FormattedMessage } from 'react-intl'; 199 | 200 | ; 201 | 202 | ↓ ↓ ↓ ↓ ↓ ↓ 203 | 204 | import { FormattedMessage } from 'react-intl'; 205 | ; 206 | 207 | `; 208 | 209 | exports[`removePrefix = /__fixtures__/ default: default 1`] = ` 210 | 211 | import { FormattedMessage } from 'react-intl'; 212 | 213 | ; 214 | 215 | ↓ ↓ ↓ ↓ ↓ ↓ 216 | 217 | import { FormattedMessage } from 'react-intl'; 218 | ; 219 | 220 | `; 221 | 222 | exports[`removePrefix = false default: default 1`] = ` 223 | 224 | import { FormattedMessage } from 'react-intl'; 225 | 226 | ; 227 | 228 | ↓ ↓ ↓ ↓ ↓ ↓ 229 | 230 | import { FormattedMessage } from 'react-intl'; 231 | ; 232 | 233 | `; 234 | 235 | exports[`removePrefix = true, includeExportName = all default: default 1`] = ` 236 | 237 | import { FormattedMessage } from 'react-intl'; 238 | 239 | ; 240 | 241 | ↓ ↓ ↓ ↓ ↓ ↓ 242 | 243 | import { FormattedMessage } from 'react-intl'; 244 | ; 245 | 246 | `; 247 | 248 | exports[`removePrefix = true, includeExportName = true default: default 1`] = ` 249 | 250 | import { FormattedMessage } from 'react-intl'; 251 | 252 | ; 253 | 254 | ↓ ↓ ↓ ↓ ↓ ↓ 255 | 256 | import { FormattedMessage } from 'react-intl'; 257 | ; 258 | 259 | `; 260 | 261 | exports[`useKey = true default: default 1`] = ` 262 | 263 | import { FormattedMessage } from 'react-intl'; 264 | 265 | ; 266 | 267 | ↓ ↓ ↓ ↓ ↓ ↓ 268 | 269 | import { FormattedMessage } from 'react-intl'; 270 | ; 271 | 272 | `; 273 | 274 | exports[`useKey = true using key: using key 1`] = ` 275 | 276 | import { FormattedMessage } from 'react-intl'; 277 | 278 | ; 279 | 280 | ↓ ↓ ↓ ↓ ↓ ↓ 281 | 282 | import { FormattedMessage } from 'react-intl'; 283 | ; 284 | 285 | `; 286 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/hook.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`default multiple uses: multiple uses 1`] = ` 4 | 5 | import { useIntl } from 'react-intl'; 6 | 7 | intl.formatMessage({ defaultMessage: "hello" }); 8 | intl.formatMessage({ defaultMessage: "hello" }); 9 | intl.formatMessage({ defaultMessage: "some other message" }); 10 | 11 | ↓ ↓ ↓ ↓ ↓ ↓ 12 | 13 | import { useIntl } from 'react-intl'; 14 | intl.formatMessage({ 15 | defaultMessage: "hello", 16 | "id": "src.__fixtures__.613153351" 17 | }); 18 | intl.formatMessage({ 19 | defaultMessage: "hello", 20 | "id": "src.__fixtures__.613153351" 21 | }); 22 | intl.formatMessage({ 23 | defaultMessage: "some other message", 24 | "id": "src.__fixtures__.831373691" 25 | }); 26 | 27 | `; 28 | 29 | exports[`default some supported use cases: some supported use cases 1`] = ` 30 | 31 | import { useIntl } from 'react-intl'; 32 | 33 | const Component2 = () => { 34 | const intl = useIntl(); 35 | const label = intl.formatMessage({ defaultMessage: "hello" }); 36 | return ( 37 | 40 | ); 41 | }; 42 | 43 | ↓ ↓ ↓ ↓ ↓ ↓ 44 | 45 | import { useIntl } from 'react-intl'; 46 | 47 | const Component2 = () => { 48 | const intl = useIntl(); 49 | const label = intl.formatMessage({ 50 | defaultMessage: "hello", 51 | "id": "src.__fixtures__.613153351" 52 | }); 53 | return ; 62 | }; 63 | 64 | `; 65 | 66 | exports[`default with FormattedMessage imported as something else: with FormattedMessage imported as something else 1`] = ` 67 | 68 | import { useIntl as i18n } from 'react-intl'; 69 | 70 | intl.formatMessage({ defaultMessage: "i18n" }); 71 | 72 | ↓ ↓ ↓ ↓ ↓ ↓ 73 | 74 | import { useIntl as i18n } from 'react-intl'; 75 | intl.formatMessage({ 76 | defaultMessage: "i18n", 77 | "id": "src.__fixtures__.94348014" 78 | }); 79 | 80 | `; 81 | 82 | exports[`default with a value interpolated in the message: with a value interpolated in the message 1`] = ` 83 | 84 | import { useIntl } from 'react-intl'; 85 | 86 | intl.formatMessage({ defaultMessage: \`template string 2\` }); 87 | 88 | ↓ ↓ ↓ ↓ ↓ ↓ 89 | 90 | import { useIntl } from 'react-intl'; 91 | intl.formatMessage({ 92 | defaultMessage: \`template string 2\`, 93 | "id": "src.__fixtures__.1045198380" 94 | }); 95 | 96 | `; 97 | 98 | exports[`default with a variable as the defaultMessage: with a variable as the defaultMessage 1`] = ` 99 | 100 | import { useIntl } from 'react-intl'; 101 | 102 | const message = "variable message"; 103 | 104 | intl.formatMessage({ defaultMessage: message }); 105 | 106 | ↓ ↓ ↓ ↓ ↓ ↓ 107 | 108 | import { useIntl } from 'react-intl'; 109 | const message = "variable message"; 110 | intl.formatMessage({ 111 | defaultMessage: message, 112 | "id": "src.__fixtures__.3082794952" 113 | }); 114 | 115 | `; 116 | 117 | exports[`default with a variable as the defaultMessage: with a variable as the defaultMessage 2`] = ` 118 | 119 | import { useIntl } from 'react-intl'; 120 | import { message } from './messages' 121 | 122 | intl.formatMessage(messages); 123 | 124 | ↓ ↓ ↓ ↓ ↓ ↓ 125 | 126 | import { useIntl } from 'react-intl'; 127 | import { message } from './messages'; 128 | intl.formatMessage(messages); 129 | 130 | `; 131 | 132 | exports[`default with custom properties in formatMessage call: with custom properties in formatMessage call 1`] = ` 133 | 134 | import { useIntl } from 'react-intl'; 135 | 136 | intl.formatMessage({ defaultMessage: "custom prop", other: 123 }); 137 | 138 | ↓ ↓ ↓ ↓ ↓ ↓ 139 | 140 | import { useIntl } from 'react-intl'; 141 | intl.formatMessage({ 142 | defaultMessage: "custom prop", 143 | "id": "src.__fixtures__.2983810267", 144 | other: 123 145 | }); 146 | 147 | `; 148 | 149 | exports[`default with injectIntl: with injectIntl 1`] = ` 150 | 151 | import { injectIntl } from 'react-intl'; 152 | function App({ intl }) { 153 | return
{intl.formatMessage({ defaultMessage: 'hello' })}
154 | } 155 | 156 | export default injectIntl(App) 157 | 158 | ↓ ↓ ↓ ↓ ↓ ↓ 159 | 160 | import { injectIntl } from 'react-intl'; 161 | 162 | function App({ 163 | intl 164 | }) { 165 | return
{intl.formatMessage({ 166 | defaultMessage: 'hello', 167 | "id": "src.__fixtures__.613153351" 168 | })}
; 169 | } 170 | 171 | export default injectIntl(App); 172 | 173 | `; 174 | 175 | exports[`default with useIntl hook imported: with useIntl hook imported 1`] = ` 176 | 177 | import { useIntl } from 'react-intl'; 178 | 179 | intl.formatMessage({ defaultMessage: "hello" }); 180 | 181 | ↓ ↓ ↓ ↓ ↓ ↓ 182 | 183 | import { useIntl } from 'react-intl'; 184 | intl.formatMessage({ 185 | defaultMessage: "hello", 186 | "id": "src.__fixtures__.613153351" 187 | }); 188 | 189 | `; 190 | 191 | exports[`default withKeyFlag: withKeyFlag 1`] = ` 192 | 193 | import { useIntl } from 'react-intl'; 194 | intl.formatMessage({ 195 | key: 'foobar', 196 | defaultMessage: 'hello' 197 | }); 198 | 199 | ↓ ↓ ↓ ↓ ↓ ↓ 200 | 201 | import { useIntl } from 'react-intl'; 202 | intl.formatMessage({ 203 | key: 'foobar', 204 | defaultMessage: 'hello', 205 | "id": "src.__fixtures__.613153351" 206 | }); 207 | 208 | `; 209 | 210 | exports[`filebase = true with useIntl hook imported: with useIntl hook imported 1`] = ` 211 | 212 | import { useIntl } from 'react-intl'; 213 | 214 | intl.formatMessage({ defaultMessage: "hello" }); 215 | 216 | ↓ ↓ ↓ ↓ ↓ ↓ 217 | 218 | import { useIntl } from 'react-intl'; 219 | intl.formatMessage({ 220 | defaultMessage: "hello", 221 | "id": "src.__fixtures__.messages.613153351" 222 | }); 223 | 224 | `; 225 | 226 | exports[`removePrefix = "src" with useIntl hook imported: with useIntl hook imported 1`] = ` 227 | 228 | import { useIntl } from 'react-intl'; 229 | 230 | intl.formatMessage({ defaultMessage: "hello" }); 231 | 232 | ↓ ↓ ↓ ↓ ↓ ↓ 233 | 234 | import { useIntl } from 'react-intl'; 235 | intl.formatMessage({ 236 | defaultMessage: "hello", 237 | "id": "__fixtures__.613153351" 238 | }); 239 | 240 | `; 241 | 242 | exports[`removePrefix = "src.__fixtures__" with useIntl hook imported: with useIntl hook imported 1`] = ` 243 | 244 | import { useIntl } from 'react-intl'; 245 | 246 | intl.formatMessage({ defaultMessage: "hello" }); 247 | 248 | ↓ ↓ ↓ ↓ ↓ ↓ 249 | 250 | import { useIntl } from 'react-intl'; 251 | intl.formatMessage({ 252 | defaultMessage: "hello", 253 | "id": "613153351" 254 | }); 255 | 256 | `; 257 | 258 | exports[`removePrefix = "src/" -- with slash with useIntl hook imported: with useIntl hook imported 1`] = ` 259 | 260 | import { useIntl } from 'react-intl'; 261 | 262 | intl.formatMessage({ defaultMessage: "hello" }); 263 | 264 | ↓ ↓ ↓ ↓ ↓ ↓ 265 | 266 | import { useIntl } from 'react-intl'; 267 | intl.formatMessage({ 268 | defaultMessage: "hello", 269 | "id": "__fixtures__.613153351" 270 | }); 271 | 272 | `; 273 | 274 | exports[`removePrefix = /__fixtures__/ with useIntl hook imported: with useIntl hook imported 1`] = ` 275 | 276 | import { useIntl } from 'react-intl'; 277 | 278 | intl.formatMessage({ defaultMessage: "hello" }); 279 | 280 | ↓ ↓ ↓ ↓ ↓ ↓ 281 | 282 | import { useIntl } from 'react-intl'; 283 | intl.formatMessage({ 284 | defaultMessage: "hello", 285 | "id": "src.613153351" 286 | }); 287 | 288 | `; 289 | 290 | exports[`removePrefix = false with useIntl hook imported: with useIntl hook imported 1`] = ` 291 | 292 | import { useIntl } from 'react-intl'; 293 | 294 | intl.formatMessage({ defaultMessage: "hello" }); 295 | 296 | ↓ ↓ ↓ ↓ ↓ ↓ 297 | 298 | import { useIntl } from 'react-intl'; 299 | intl.formatMessage({ 300 | defaultMessage: "hello", 301 | "id": "src.__fixtures__.613153351" 302 | }); 303 | 304 | `; 305 | 306 | exports[`useKey = true with useIntl hook imported: with useIntl hook imported 1`] = ` 307 | 308 | import { useIntl } from 'react-intl'; 309 | 310 | intl.formatMessage({ defaultMessage: "hello" }); 311 | 312 | ↓ ↓ ↓ ↓ ↓ ↓ 313 | 314 | import { useIntl } from 'react-intl'; 315 | intl.formatMessage({ 316 | defaultMessage: "hello", 317 | "id": "src.__fixtures__.613153351" 318 | }); 319 | 320 | `; 321 | 322 | exports[`useKey = true withKeyFlag: withKeyFlag 1`] = ` 323 | 324 | import { useIntl } from 'react-intl'; 325 | intl.formatMessage({ 326 | key: 'foobar', 327 | defaultMessage: 'hello' 328 | }); 329 | 330 | ↓ ↓ ↓ ↓ ↓ ↓ 331 | 332 | import { useIntl } from 'react-intl'; 333 | intl.formatMessage({ 334 | key: 'foobar', 335 | defaultMessage: 'hello', 336 | "id": "src.__fixtures__.foobar" 337 | }); 338 | 339 | `; 340 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/index.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`default Object: Object 1`] = ` 4 | 5 | import { defineMessages } from 'react-intl' 6 | 7 | defineMessages({ 8 | new: { 9 | id: 'this is id', 10 | defaultMessage: 'id', 11 | }, 12 | world: { 13 | defaultMessage: 'world', 14 | }, 15 | headerTitle: { 16 | defaultMessage: 'Welcome to dashboard {name}!', 17 | description: 'Message to greet the user.', 18 | }, 19 | }) 20 | 21 | ↓ ↓ ↓ ↓ ↓ ↓ 22 | 23 | import { defineMessages } from 'react-intl'; 24 | defineMessages({ 25 | new: { 26 | id: 'this is id', 27 | defaultMessage: 'id' 28 | }, 29 | world: { 30 | "id": "src.__fixtures__.world", 31 | defaultMessage: 'world' 32 | }, 33 | headerTitle: { 34 | "id": "src.__fixtures__.headerTitle", 35 | defaultMessage: 'Welcome to dashboard {name}!', 36 | description: 'Message to greet the user.' 37 | } 38 | }); 39 | 40 | `; 41 | 42 | exports[`default default: default 1`] = ` 43 | 44 | import { defineMessages } from 'react-intl' 45 | 46 | export default defineMessages({ 47 | hello: 'hello', 48 | }) 49 | 50 | ↓ ↓ ↓ ↓ ↓ ↓ 51 | 52 | import { defineMessages } from 'react-intl'; 53 | export default defineMessages({ 54 | hello: { 55 | "id": "src.__fixtures__.hello", 56 | "defaultMessage": 'hello' 57 | } 58 | }); 59 | 60 | `; 61 | 62 | exports[`default eval string: eval string 1`] = ` 63 | 64 | import { defineMessages } from 'react-intl' 65 | 66 | export default defineMessages({ 67 | hello: 'hello' + 'world', 68 | }) 69 | 70 | ↓ ↓ ↓ ↓ ↓ ↓ 71 | 72 | import { defineMessages } from 'react-intl'; 73 | export default defineMessages({ 74 | hello: { 75 | "id": "src.__fixtures__.helloworld", 76 | "defaultMessage": "helloworld" 77 | } 78 | }); 79 | 80 | `; 81 | 82 | exports[`default import as: import as 1`] = ` 83 | 84 | import { defineMessages as m } from 'react-intl' 85 | 86 | m({ 87 | hello: 'hello' 88 | }) 89 | 90 | ↓ ↓ ↓ ↓ ↓ ↓ 91 | 92 | import { defineMessages as m } from 'react-intl'; 93 | m({ 94 | hello: { 95 | "id": "src.__fixtures__.hello", 96 | "defaultMessage": 'hello' 97 | } 98 | }); 99 | 100 | `; 101 | 102 | exports[`default leading comment with description: leading comment with description 1`] = ` 103 | 104 | import { defineMessages } from 'react-intl' 105 | 106 | export default defineMessages({ 107 | 108 | // This comment should not be used 109 | world: { 110 | defaultMessage: 'hello world', 111 | description: 'The hello world', 112 | } 113 | }) 114 | 115 | ↓ ↓ ↓ ↓ ↓ ↓ 116 | 117 | import { defineMessages } from 'react-intl'; 118 | export default defineMessages({ 119 | // This comment should not be used 120 | world: { 121 | "id": "src.__fixtures__.world", 122 | defaultMessage: 'hello world', 123 | description: 'The hello world' 124 | } 125 | }); 126 | 127 | `; 128 | 129 | exports[`default leading comment: leading comment 1`] = ` 130 | 131 | import { defineMessages } from 'react-intl' 132 | 133 | export default defineMessages({ 134 | // The main Hello of our app. 135 | hello: 'hello', 136 | 137 | // Another Hello, 138 | // multiline this time 139 | world: { 140 | id: 'hello.world', 141 | defaultMessage: 'hello world', 142 | } 143 | }) 144 | 145 | ↓ ↓ ↓ ↓ ↓ ↓ 146 | 147 | import { defineMessages } from 'react-intl'; 148 | export default defineMessages({ 149 | // The main Hello of our app. 150 | hello: { 151 | "id": "src.__fixtures__.hello", 152 | "defaultMessage": 'hello', 153 | "description": "The main Hello of our app." 154 | }, 155 | // Another Hello, 156 | // multiline this time 157 | world: { 158 | id: 'hello.world', 159 | defaultMessage: 'hello world', 160 | "description": "Another Hello,\\nmultiline this time" 161 | } 162 | }); 163 | 164 | `; 165 | 166 | exports[`default multi export: multi export 1`] = ` 167 | 168 | import { defineMessages } from 'react-intl' 169 | 170 | export const extra = defineMessages({ 171 | hello: 'hello world extra' 172 | }) 173 | 174 | export default defineMessages({ 175 | hello: 'hello world', 176 | }) 177 | 178 | ↓ ↓ ↓ ↓ ↓ ↓ 179 | 180 | import { defineMessages } from 'react-intl'; 181 | export const extra = defineMessages({ 182 | hello: { 183 | "id": "src.__fixtures__.hello", 184 | "defaultMessage": 'hello world extra' 185 | } 186 | }); 187 | export default defineMessages({ 188 | hello: { 189 | "id": "src.__fixtures__.hello", 190 | "defaultMessage": 'hello world' 191 | } 192 | }); 193 | 194 | `; 195 | 196 | exports[`default not transform if callee is not identifier: not transform if callee is not identifier 1`] = ` 197 | 198 | import { defineMessages } from 'react-intl' 199 | 200 | const m = [defineMessages] 201 | 202 | export default m[0]({ 203 | hello: 'hello world' 204 | }) 205 | 206 | ↓ ↓ ↓ ↓ ↓ ↓ 207 | 208 | import { defineMessages } from 'react-intl'; 209 | const m = [defineMessages]; 210 | export default m[0]({ 211 | hello: 'hello world' 212 | }); 213 | 214 | `; 215 | 216 | exports[`default not transform if defineMessages is not imported: not transform if defineMessages is not imported 1`] = ` 217 | 218 | import any from 'any-module' 219 | 220 | export default defineMessages({ 221 | hello: 'hello' 222 | }) 223 | 224 | ↓ ↓ ↓ ↓ ↓ ↓ 225 | 226 | import any from 'any-module'; 227 | export default defineMessages({ 228 | hello: 'hello' 229 | }); 230 | 231 | `; 232 | 233 | exports[`default not transform when defineMessages argumens is empty: not transform when defineMessages argumens is empty 1`] = ` 234 | 235 | import { defineMessages } from 'react-intl' 236 | 237 | export default defineMessages() 238 | 239 | ↓ ↓ ↓ ↓ ↓ ↓ 240 | 241 | import { defineMessages } from 'react-intl'; 242 | export default defineMessages(); 243 | 244 | `; 245 | 246 | exports[`default not transform when defineMessages argumens is not object: not transform when defineMessages argumens is not object 1`] = ` 247 | 248 | import { defineMessages } from 'react-intl' 249 | 250 | export default defineMessages(1) 251 | 252 | ↓ ↓ ↓ ↓ ↓ ↓ 253 | 254 | import { defineMessages } from 'react-intl'; 255 | export default defineMessages(1); 256 | 257 | `; 258 | 259 | exports[`default not transfrom when the variable can not be found: not transfrom when the variable can not be found 1`] = ` 260 | 261 | import { defineMessages } from 'react-intl' 262 | 263 | export default defineMessages(messages) 264 | 265 | ↓ ↓ ↓ ↓ ↓ ↓ 266 | 267 | import { defineMessages } from 'react-intl'; 268 | export default defineMessages(messages); 269 | 270 | `; 271 | 272 | exports[`default string literal: string literal 1`] = ` 273 | 274 | import { defineMessages } from 'react-intl' 275 | 276 | defineMessages({ 277 | 'hello': 'hello world' 278 | }) 279 | 280 | ↓ ↓ ↓ ↓ ↓ ↓ 281 | 282 | import { defineMessages } from 'react-intl'; 283 | defineMessages({ 284 | 'hello': { 285 | "id": "src.__fixtures__.hello", 286 | "defaultMessage": 'hello world' 287 | } 288 | }); 289 | 290 | `; 291 | 292 | exports[`default when using the variable: when using the variable 1`] = ` 293 | 294 | import { defineMessages } from 'react-intl' 295 | 296 | const messages = {hello: 'hello'} 297 | 298 | export default defineMessages(messages) 299 | 300 | ↓ ↓ ↓ ↓ ↓ ↓ 301 | 302 | import { defineMessages } from 'react-intl'; 303 | const messages = { 304 | hello: { 305 | "id": "src.__fixtures__.hello", 306 | "defaultMessage": 'hello' 307 | } 308 | }; 309 | export default defineMessages(messages); 310 | 311 | `; 312 | 313 | exports[`default with include value: with include value 1`] = ` 314 | 315 | import { defineMessages } from 'react-intl' 316 | 317 | defineMessages({ 318 | hello: \`hello world \${1}\`, 319 | }) 320 | 321 | ↓ ↓ ↓ ↓ ↓ ↓ 322 | 323 | import { defineMessages } from 'react-intl'; 324 | defineMessages({ 325 | hello: { 326 | "id": "src.__fixtures__.hello", 327 | "defaultMessage": \`hello world \${1}\` 328 | } 329 | }); 330 | 331 | `; 332 | 333 | exports[`default with other func: with other func 1`] = ` 334 | 335 | import { defineMessages } from 'react-intl' 336 | 337 | defineMessages({ 338 | hello: 'hello', 339 | }) 340 | 341 | hello({ 342 | id: 'hoge', 343 | }) 344 | 345 | ↓ ↓ ↓ ↓ ↓ ↓ 346 | 347 | import { defineMessages } from 'react-intl'; 348 | defineMessages({ 349 | hello: { 350 | "id": "src.__fixtures__.hello", 351 | "defaultMessage": 'hello' 352 | } 353 | }); 354 | hello({ 355 | id: 'hoge' 356 | }); 357 | 358 | `; 359 | 360 | exports[`default with other specifier: with other specifier 1`] = ` 361 | 362 | import { defineMessages, FormattedMessage } from 'react-intl' 363 | 364 | export default defineMessages({ 365 | hello: 'hello world', 366 | }) 367 | 368 | ↓ ↓ ↓ ↓ ↓ ↓ 369 | 370 | import { defineMessages, FormattedMessage } from 'react-intl'; 371 | export default defineMessages({ 372 | hello: { 373 | "id": "src.__fixtures__.hello", 374 | "defaultMessage": 'hello world' 375 | } 376 | }); 377 | 378 | `; 379 | 380 | exports[`extractComments = false default: default 1`] = ` 381 | 382 | import { defineMessages } from 'react-intl' 383 | 384 | export default defineMessages({ 385 | hello: 'hello', 386 | }) 387 | 388 | ↓ ↓ ↓ ↓ ↓ ↓ 389 | 390 | import { defineMessages } from 'react-intl'; 391 | export default defineMessages({ 392 | hello: { 393 | "id": "src.__fixtures__.hello", 394 | "defaultMessage": 'hello' 395 | } 396 | }); 397 | 398 | `; 399 | 400 | exports[`extractComments = false leading comment with description: leading comment with description 1`] = ` 401 | 402 | import { defineMessages } from 'react-intl' 403 | 404 | export default defineMessages({ 405 | 406 | // This comment should not be used 407 | world: { 408 | defaultMessage: 'hello world', 409 | description: 'The hello world', 410 | } 411 | }) 412 | 413 | ↓ ↓ ↓ ↓ ↓ ↓ 414 | 415 | import { defineMessages } from 'react-intl'; 416 | export default defineMessages({ 417 | // This comment should not be used 418 | world: { 419 | "id": "src.__fixtures__.world", 420 | defaultMessage: 'hello world', 421 | description: 'The hello world' 422 | } 423 | }); 424 | 425 | `; 426 | 427 | exports[`extractComments = false leading comment: leading comment 1`] = ` 428 | 429 | import { defineMessages } from 'react-intl' 430 | 431 | export default defineMessages({ 432 | // The main Hello of our app. 433 | hello: 'hello', 434 | 435 | // Another Hello, 436 | // multiline this time 437 | world: { 438 | id: 'hello.world', 439 | defaultMessage: 'hello world', 440 | } 441 | }) 442 | 443 | ↓ ↓ ↓ ↓ ↓ ↓ 444 | 445 | import { defineMessages } from 'react-intl'; 446 | export default defineMessages({ 447 | // The main Hello of our app. 448 | hello: { 449 | "id": "src.__fixtures__.hello", 450 | "defaultMessage": 'hello' 451 | }, 452 | // Another Hello, 453 | // multiline this time 454 | world: { 455 | id: 'hello.world', 456 | defaultMessage: 'hello world' 457 | } 458 | }); 459 | 460 | `; 461 | 462 | exports[`filebase = true default: default 1`] = ` 463 | 464 | import { defineMessages } from 'react-intl' 465 | 466 | export default defineMessages({ 467 | hello: 'hello', 468 | }) 469 | 470 | ↓ ↓ ↓ ↓ ↓ ↓ 471 | 472 | import { defineMessages } from 'react-intl'; 473 | export default defineMessages({ 474 | hello: { 475 | "id": "src.__fixtures__.messages.hello", 476 | "defaultMessage": 'hello' 477 | } 478 | }); 479 | 480 | `; 481 | 482 | exports[`includeExportName = all default: default 1`] = ` 483 | 484 | import { defineMessages } from 'react-intl' 485 | 486 | export default defineMessages({ 487 | hello: 'hello', 488 | }) 489 | 490 | ↓ ↓ ↓ ↓ ↓ ↓ 491 | 492 | import { defineMessages } from 'react-intl'; 493 | export default defineMessages({ 494 | hello: { 495 | "id": "src.__fixtures__.default.hello", 496 | "defaultMessage": 'hello' 497 | } 498 | }); 499 | 500 | `; 501 | 502 | exports[`includeExportName = all multi export: multi export 1`] = ` 503 | 504 | import { defineMessages } from 'react-intl' 505 | 506 | export const extra = defineMessages({ 507 | hello: 'hello world extra' 508 | }) 509 | 510 | export default defineMessages({ 511 | hello: 'hello world', 512 | }) 513 | 514 | ↓ ↓ ↓ ↓ ↓ ↓ 515 | 516 | import { defineMessages } from 'react-intl'; 517 | export const extra = defineMessages({ 518 | hello: { 519 | "id": "src.__fixtures__.extra.hello", 520 | "defaultMessage": 'hello world extra' 521 | } 522 | }); 523 | export default defineMessages({ 524 | hello: { 525 | "id": "src.__fixtures__.default.hello", 526 | "defaultMessage": 'hello world' 527 | } 528 | }); 529 | 530 | `; 531 | 532 | exports[`includeExportName = true default: default 1`] = ` 533 | 534 | import { defineMessages } from 'react-intl' 535 | 536 | export default defineMessages({ 537 | hello: 'hello', 538 | }) 539 | 540 | ↓ ↓ ↓ ↓ ↓ ↓ 541 | 542 | import { defineMessages } from 'react-intl'; 543 | export default defineMessages({ 544 | hello: { 545 | "id": "src.__fixtures__.hello", 546 | "defaultMessage": 'hello' 547 | } 548 | }); 549 | 550 | `; 551 | 552 | exports[`includeExportName = true multi export: multi export 1`] = ` 553 | 554 | import { defineMessages } from 'react-intl' 555 | 556 | export const extra = defineMessages({ 557 | hello: 'hello world extra' 558 | }) 559 | 560 | export default defineMessages({ 561 | hello: 'hello world', 562 | }) 563 | 564 | ↓ ↓ ↓ ↓ ↓ ↓ 565 | 566 | import { defineMessages } from 'react-intl'; 567 | export const extra = defineMessages({ 568 | hello: { 569 | "id": "src.__fixtures__.extra.hello", 570 | "defaultMessage": 'hello world extra' 571 | } 572 | }); 573 | export default defineMessages({ 574 | hello: { 575 | "id": "src.__fixtures__.hello", 576 | "defaultMessage": 'hello world' 577 | } 578 | }); 579 | 580 | `; 581 | 582 | exports[`moduleSourceNameTest default: default 1`] = ` 583 | 584 | import { defineMessages } from 'react-intl' 585 | 586 | export default defineMessages({ 587 | hello: 'hello', 588 | }) 589 | 590 | ↓ ↓ ↓ ↓ ↓ ↓ 591 | 592 | import { defineMessages } from 'react-intl'; 593 | export default defineMessages({ 594 | hello: 'hello' 595 | }); 596 | 597 | `; 598 | 599 | exports[`moduleSourceNameTest moduleSourceName: moduleSourceName 1`] = ` 600 | 601 | import { defineMessages } from 'gatsby-plugin-intl' 602 | 603 | export default defineMessages({ 604 | hello: 'hello', 605 | }) 606 | 607 | ↓ ↓ ↓ ↓ ↓ ↓ 608 | 609 | import { defineMessages } from 'gatsby-plugin-intl'; 610 | export default defineMessages({ 611 | hello: { 612 | "id": "src.__fixtures__.hello", 613 | "defaultMessage": 'hello' 614 | } 615 | }); 616 | 617 | `; 618 | 619 | exports[`relativeTo = "" default: default 1`] = ` 620 | 621 | import { defineMessages } from 'react-intl' 622 | 623 | export default defineMessages({ 624 | hello: 'hello', 625 | }) 626 | 627 | ↓ ↓ ↓ ↓ ↓ ↓ 628 | 629 | import { defineMessages } from 'react-intl'; 630 | export default defineMessages({ 631 | hello: { 632 | "id": "src.__fixtures__.hello", 633 | "defaultMessage": 'hello' 634 | } 635 | }); 636 | 637 | `; 638 | 639 | exports[`relativeTo = "" multi export: multi export 1`] = ` 640 | 641 | import { defineMessages } from 'react-intl' 642 | 643 | export const extra = defineMessages({ 644 | hello: 'hello world extra' 645 | }) 646 | 647 | export default defineMessages({ 648 | hello: 'hello world', 649 | }) 650 | 651 | ↓ ↓ ↓ ↓ ↓ ↓ 652 | 653 | import { defineMessages } from 'react-intl'; 654 | export const extra = defineMessages({ 655 | hello: { 656 | "id": "src.__fixtures__.hello", 657 | "defaultMessage": 'hello world extra' 658 | } 659 | }); 660 | export default defineMessages({ 661 | hello: { 662 | "id": "src.__fixtures__.hello", 663 | "defaultMessage": 'hello world' 664 | } 665 | }); 666 | 667 | `; 668 | 669 | exports[`relativeTo = "../" default: default 1`] = ` 670 | 671 | import { defineMessages } from 'react-intl' 672 | 673 | export default defineMessages({ 674 | hello: 'hello', 675 | }) 676 | 677 | ↓ ↓ ↓ ↓ ↓ ↓ 678 | 679 | import { defineMessages } from 'react-intl'; 680 | export default defineMessages({ 681 | hello: { 682 | "id": "babel-plugin-react-intl-auto.src.__fixtures__.hello", 683 | "defaultMessage": 'hello' 684 | } 685 | }); 686 | 687 | `; 688 | 689 | exports[`relativeTo = "../" multi export: multi export 1`] = ` 690 | 691 | import { defineMessages } from 'react-intl' 692 | 693 | export const extra = defineMessages({ 694 | hello: 'hello world extra' 695 | }) 696 | 697 | export default defineMessages({ 698 | hello: 'hello world', 699 | }) 700 | 701 | ↓ ↓ ↓ ↓ ↓ ↓ 702 | 703 | import { defineMessages } from 'react-intl'; 704 | export const extra = defineMessages({ 705 | hello: { 706 | "id": "babel-plugin-react-intl-auto.src.__fixtures__.hello", 707 | "defaultMessage": 'hello world extra' 708 | } 709 | }); 710 | export default defineMessages({ 711 | hello: { 712 | "id": "babel-plugin-react-intl-auto.src.__fixtures__.hello", 713 | "defaultMessage": 'hello world' 714 | } 715 | }); 716 | 717 | `; 718 | 719 | exports[`removePrefix = "src" default: default 1`] = ` 720 | 721 | import { defineMessages } from 'react-intl' 722 | 723 | export default defineMessages({ 724 | hello: 'hello', 725 | }) 726 | 727 | ↓ ↓ ↓ ↓ ↓ ↓ 728 | 729 | import { defineMessages } from 'react-intl'; 730 | export default defineMessages({ 731 | hello: { 732 | "id": "__fixtures__.hello", 733 | "defaultMessage": 'hello' 734 | } 735 | }); 736 | 737 | `; 738 | 739 | exports[`removePrefix = "src.__fixtures__" default: default 1`] = ` 740 | 741 | import { defineMessages } from 'react-intl' 742 | 743 | export default defineMessages({ 744 | hello: 'hello', 745 | }) 746 | 747 | ↓ ↓ ↓ ↓ ↓ ↓ 748 | 749 | import { defineMessages } from 'react-intl'; 750 | export default defineMessages({ 751 | hello: { 752 | "id": "hello", 753 | "defaultMessage": 'hello' 754 | } 755 | }); 756 | 757 | `; 758 | 759 | exports[`removePrefix = "src.__fixtures__", includeExportName = true default: default 1`] = ` 760 | 761 | import { defineMessages } from 'react-intl' 762 | 763 | export default defineMessages({ 764 | hello: 'hello', 765 | }) 766 | 767 | ↓ ↓ ↓ ↓ ↓ ↓ 768 | 769 | import { defineMessages } from 'react-intl'; 770 | export default defineMessages({ 771 | hello: { 772 | "id": "hello", 773 | "defaultMessage": 'hello' 774 | } 775 | }); 776 | 777 | `; 778 | 779 | exports[`removePrefix = "src.__fixtures__", includeExportName = true multi export: multi export 1`] = ` 780 | 781 | import { defineMessages } from 'react-intl' 782 | 783 | export const extra = defineMessages({ 784 | hello: 'hello world extra' 785 | }) 786 | 787 | export default defineMessages({ 788 | hello: 'hello world', 789 | }) 790 | 791 | ↓ ↓ ↓ ↓ ↓ ↓ 792 | 793 | import { defineMessages } from 'react-intl'; 794 | export const extra = defineMessages({ 795 | hello: { 796 | "id": "extra.hello", 797 | "defaultMessage": 'hello world extra' 798 | } 799 | }); 800 | export default defineMessages({ 801 | hello: { 802 | "id": "hello", 803 | "defaultMessage": 'hello world' 804 | } 805 | }); 806 | 807 | `; 808 | 809 | exports[`removePrefix = "src/" -- with slash default: default 1`] = ` 810 | 811 | import { defineMessages } from 'react-intl' 812 | 813 | export default defineMessages({ 814 | hello: 'hello', 815 | }) 816 | 817 | ↓ ↓ ↓ ↓ ↓ ↓ 818 | 819 | import { defineMessages } from 'react-intl'; 820 | export default defineMessages({ 821 | hello: { 822 | "id": "__fixtures__.hello", 823 | "defaultMessage": 'hello' 824 | } 825 | }); 826 | 827 | `; 828 | 829 | exports[`removePrefix = /__fixtures__/ default: default 1`] = ` 830 | 831 | import { defineMessages } from 'react-intl' 832 | 833 | export default defineMessages({ 834 | hello: 'hello', 835 | }) 836 | 837 | ↓ ↓ ↓ ↓ ↓ ↓ 838 | 839 | import { defineMessages } from 'react-intl'; 840 | export default defineMessages({ 841 | hello: { 842 | "id": "_.hello", 843 | "defaultMessage": 'hello' 844 | } 845 | }); 846 | 847 | `; 848 | 849 | exports[`removePrefix = false default: default 1`] = ` 850 | 851 | import { defineMessages } from 'react-intl' 852 | 853 | export default defineMessages({ 854 | hello: 'hello', 855 | }) 856 | 857 | ↓ ↓ ↓ ↓ ↓ ↓ 858 | 859 | import { defineMessages } from 'react-intl'; 860 | export default defineMessages({ 861 | hello: { 862 | "id": "src.__fixtures__.hello", 863 | "defaultMessage": 'hello' 864 | } 865 | }); 866 | 867 | `; 868 | 869 | exports[`removePrefix = false multi export: multi export 1`] = ` 870 | 871 | import { defineMessages } from 'react-intl' 872 | 873 | export const extra = defineMessages({ 874 | hello: 'hello world extra' 875 | }) 876 | 877 | export default defineMessages({ 878 | hello: 'hello world', 879 | }) 880 | 881 | ↓ ↓ ↓ ↓ ↓ ↓ 882 | 883 | import { defineMessages } from 'react-intl'; 884 | export const extra = defineMessages({ 885 | hello: { 886 | "id": "src.__fixtures__.hello", 887 | "defaultMessage": 'hello world extra' 888 | } 889 | }); 890 | export default defineMessages({ 891 | hello: { 892 | "id": "src.__fixtures__.hello", 893 | "defaultMessage": 'hello world' 894 | } 895 | }); 896 | 897 | `; 898 | 899 | exports[`removePrefix = true, includeExportName = all default: default 1`] = ` 900 | 901 | import { defineMessages } from 'react-intl' 902 | 903 | export default defineMessages({ 904 | hello: 'hello', 905 | }) 906 | 907 | ↓ ↓ ↓ ↓ ↓ ↓ 908 | 909 | import { defineMessages } from 'react-intl'; 910 | export default defineMessages({ 911 | hello: { 912 | "id": "default.hello", 913 | "defaultMessage": 'hello' 914 | } 915 | }); 916 | 917 | `; 918 | 919 | exports[`removePrefix = true, includeExportName = all multi export: multi export 1`] = ` 920 | 921 | import { defineMessages } from 'react-intl' 922 | 923 | export const extra = defineMessages({ 924 | hello: 'hello world extra' 925 | }) 926 | 927 | export default defineMessages({ 928 | hello: 'hello world', 929 | }) 930 | 931 | ↓ ↓ ↓ ↓ ↓ ↓ 932 | 933 | import { defineMessages } from 'react-intl'; 934 | export const extra = defineMessages({ 935 | hello: { 936 | "id": "extra.hello", 937 | "defaultMessage": 'hello world extra' 938 | } 939 | }); 940 | export default defineMessages({ 941 | hello: { 942 | "id": "default.hello", 943 | "defaultMessage": 'hello world' 944 | } 945 | }); 946 | 947 | `; 948 | 949 | exports[`removePrefix = true, includeExportName = true default: default 1`] = ` 950 | 951 | import { defineMessages } from 'react-intl' 952 | 953 | export default defineMessages({ 954 | hello: 'hello', 955 | }) 956 | 957 | ↓ ↓ ↓ ↓ ↓ ↓ 958 | 959 | import { defineMessages } from 'react-intl'; 960 | export default defineMessages({ 961 | hello: { 962 | "id": "hello", 963 | "defaultMessage": 'hello' 964 | } 965 | }); 966 | 967 | `; 968 | 969 | exports[`removePrefix = true, includeExportName = true multi export: multi export 1`] = ` 970 | 971 | import { defineMessages } from 'react-intl' 972 | 973 | export const extra = defineMessages({ 974 | hello: 'hello world extra' 975 | }) 976 | 977 | export default defineMessages({ 978 | hello: 'hello world', 979 | }) 980 | 981 | ↓ ↓ ↓ ↓ ↓ ↓ 982 | 983 | import { defineMessages } from 'react-intl'; 984 | export const extra = defineMessages({ 985 | hello: { 986 | "id": "extra.hello", 987 | "defaultMessage": 'hello world extra' 988 | } 989 | }); 990 | export default defineMessages({ 991 | hello: { 992 | "id": "hello", 993 | "defaultMessage": 'hello world' 994 | } 995 | }); 996 | 997 | `; 998 | 999 | exports[`separator = "" default: default 1`] = ` 1000 | 1001 | import { defineMessages } from 'react-intl' 1002 | 1003 | export default defineMessages({ 1004 | hello: 'hello', 1005 | }) 1006 | 1007 | ↓ ↓ ↓ ↓ ↓ ↓ 1008 | 1009 | import { defineMessages } from 'react-intl'; 1010 | export default defineMessages({ 1011 | hello: { 1012 | "id": "src__fixtures__hello", 1013 | "defaultMessage": 'hello' 1014 | } 1015 | }); 1016 | 1017 | `; 1018 | 1019 | exports[`separator = "" multi export: multi export 1`] = ` 1020 | 1021 | import { defineMessages } from 'react-intl' 1022 | 1023 | export const extra = defineMessages({ 1024 | hello: 'hello world extra' 1025 | }) 1026 | 1027 | export default defineMessages({ 1028 | hello: 'hello world', 1029 | }) 1030 | 1031 | ↓ ↓ ↓ ↓ ↓ ↓ 1032 | 1033 | import { defineMessages } from 'react-intl'; 1034 | export const extra = defineMessages({ 1035 | hello: { 1036 | "id": "src__fixtures__hello", 1037 | "defaultMessage": 'hello world extra' 1038 | } 1039 | }); 1040 | export default defineMessages({ 1041 | hello: { 1042 | "id": "src__fixtures__hello", 1043 | "defaultMessage": 'hello world' 1044 | } 1045 | }); 1046 | 1047 | `; 1048 | 1049 | exports[`separator = "_" default: default 1`] = ` 1050 | 1051 | import { defineMessages } from 'react-intl' 1052 | 1053 | export default defineMessages({ 1054 | hello: 'hello', 1055 | }) 1056 | 1057 | ↓ ↓ ↓ ↓ ↓ ↓ 1058 | 1059 | import { defineMessages } from 'react-intl'; 1060 | export default defineMessages({ 1061 | hello: { 1062 | "id": "src___fixtures___hello", 1063 | "defaultMessage": 'hello' 1064 | } 1065 | }); 1066 | 1067 | `; 1068 | 1069 | exports[`separator = "_" multi export: multi export 1`] = ` 1070 | 1071 | import { defineMessages } from 'react-intl' 1072 | 1073 | export const extra = defineMessages({ 1074 | hello: 'hello world extra' 1075 | }) 1076 | 1077 | export default defineMessages({ 1078 | hello: 'hello world', 1079 | }) 1080 | 1081 | ↓ ↓ ↓ ↓ ↓ ↓ 1082 | 1083 | import { defineMessages } from 'react-intl'; 1084 | export const extra = defineMessages({ 1085 | hello: { 1086 | "id": "src___fixtures___hello", 1087 | "defaultMessage": 'hello world extra' 1088 | } 1089 | }); 1090 | export default defineMessages({ 1091 | hello: { 1092 | "id": "src___fixtures___hello", 1093 | "defaultMessage": 'hello world' 1094 | } 1095 | }); 1096 | 1097 | `; 1098 | 1099 | exports[`separator = "foo" default: default 1`] = ` 1100 | 1101 | import { defineMessages } from 'react-intl' 1102 | 1103 | export default defineMessages({ 1104 | hello: 'hello', 1105 | }) 1106 | 1107 | ↓ ↓ ↓ ↓ ↓ ↓ 1108 | 1109 | import { defineMessages } from 'react-intl'; 1110 | export default defineMessages({ 1111 | hello: { 1112 | "id": "srcfoo__fixtures__foohello", 1113 | "defaultMessage": 'hello' 1114 | } 1115 | }); 1116 | 1117 | `; 1118 | 1119 | exports[`separator = "foo" multi export: multi export 1`] = ` 1120 | 1121 | import { defineMessages } from 'react-intl' 1122 | 1123 | export const extra = defineMessages({ 1124 | hello: 'hello world extra' 1125 | }) 1126 | 1127 | export default defineMessages({ 1128 | hello: 'hello world', 1129 | }) 1130 | 1131 | ↓ ↓ ↓ ↓ ↓ ↓ 1132 | 1133 | import { defineMessages } from 'react-intl'; 1134 | export const extra = defineMessages({ 1135 | hello: { 1136 | "id": "srcfoo__fixtures__foohello", 1137 | "defaultMessage": 'hello world extra' 1138 | } 1139 | }); 1140 | export default defineMessages({ 1141 | hello: { 1142 | "id": "srcfoo__fixtures__foohello", 1143 | "defaultMessage": 'hello world' 1144 | } 1145 | }); 1146 | 1147 | `; 1148 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/injection.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`default multiple uses: multiple uses 1`] = ` 4 | 5 | import { injectIntl } from 'react-intl'; 6 | 7 | intl.formatMessage({ defaultMessage: "hello" }); 8 | intl.formatMessage({ defaultMessage: "hello" }); 9 | intl.formatMessage({ defaultMessage: "some other message" }); 10 | 11 | ↓ ↓ ↓ ↓ ↓ ↓ 12 | 13 | import { injectIntl } from 'react-intl'; 14 | intl.formatMessage({ 15 | defaultMessage: "hello", 16 | "id": "src.__fixtures__.613153351" 17 | }); 18 | intl.formatMessage({ 19 | defaultMessage: "hello", 20 | "id": "src.__fixtures__.613153351" 21 | }); 22 | intl.formatMessage({ 23 | defaultMessage: "some other message", 24 | "id": "src.__fixtures__.831373691" 25 | }); 26 | 27 | `; 28 | 29 | exports[`default some supported use cases: some supported use cases 1`] = ` 30 | 31 | import { injectIntl } from 'react-intl'; 32 | 33 | const Component2 = ({ intl }) => { 34 | const label = intl.formatMessage({ defaultMessage: "hello" }); 35 | return ( 36 | 39 | ); 40 | }; 41 | injectIntl(Components2); 42 | 43 | ↓ ↓ ↓ ↓ ↓ ↓ 44 | 45 | import { injectIntl } from 'react-intl'; 46 | 47 | const Component2 = ({ 48 | intl 49 | }) => { 50 | const label = intl.formatMessage({ 51 | defaultMessage: "hello", 52 | "id": "src.__fixtures__.613153351" 53 | }); 54 | return ; 63 | }; 64 | 65 | injectIntl(Components2); 66 | 67 | `; 68 | 69 | exports[`default with FormattedMessage imported as something else: with FormattedMessage imported as something else 1`] = ` 70 | 71 | import { injectIntl as i18n } from 'react-intl'; 72 | 73 | intl.formatMessage({ defaultMessage: "i18n" }); 74 | 75 | ↓ ↓ ↓ ↓ ↓ ↓ 76 | 77 | import { injectIntl as i18n } from 'react-intl'; 78 | intl.formatMessage({ 79 | defaultMessage: "i18n", 80 | "id": "src.__fixtures__.94348014" 81 | }); 82 | 83 | `; 84 | 85 | exports[`default with Injection API HOC imported: with Injection API HOC imported 1`] = ` 86 | 87 | import { injectIntl } from 'react-intl'; 88 | 89 | intl.formatMessage({ defaultMessage: "hello" }); 90 | 91 | ↓ ↓ ↓ ↓ ↓ ↓ 92 | 93 | import { injectIntl } from 'react-intl'; 94 | intl.formatMessage({ 95 | defaultMessage: "hello", 96 | "id": "src.__fixtures__.613153351" 97 | }); 98 | 99 | `; 100 | 101 | exports[`default with a value interpolated in the message: with a value interpolated in the message 1`] = ` 102 | 103 | import { injectIntl } from 'react-intl'; 104 | 105 | intl.formatMessage({ defaultMessage: \`template string 2\` }); 106 | 107 | ↓ ↓ ↓ ↓ ↓ ↓ 108 | 109 | import { injectIntl } from 'react-intl'; 110 | intl.formatMessage({ 111 | defaultMessage: \`template string 2\`, 112 | "id": "src.__fixtures__.1045198380" 113 | }); 114 | 115 | `; 116 | 117 | exports[`default with a variable as the defaultMessage: with a variable as the defaultMessage 1`] = ` 118 | 119 | import { injectIntl } from 'react-intl'; 120 | 121 | const message = "variable message"; 122 | 123 | intl.formatMessage({ defaultMessage: message }); 124 | 125 | ↓ ↓ ↓ ↓ ↓ ↓ 126 | 127 | import { injectIntl } from 'react-intl'; 128 | const message = "variable message"; 129 | intl.formatMessage({ 130 | defaultMessage: message, 131 | "id": "src.__fixtures__.3082794952" 132 | }); 133 | 134 | `; 135 | 136 | exports[`default with a variable as the defaultMessage: with a variable as the defaultMessage 2`] = ` 137 | 138 | import { injectIntl } from 'react-intl'; 139 | import { message } from './messages'; 140 | 141 | intl.formatMessage(message); 142 | 143 | ↓ ↓ ↓ ↓ ↓ ↓ 144 | 145 | import { injectIntl } from 'react-intl'; 146 | import { message } from './messages'; 147 | intl.formatMessage(message); 148 | 149 | `; 150 | 151 | exports[`default with custom properties in formatMessage call: with custom properties in formatMessage call 1`] = ` 152 | 153 | import { injectIntl } from 'react-intl'; 154 | 155 | intl.formatMessage({ defaultMessage: "custom prop", other: 123 }); 156 | 157 | ↓ ↓ ↓ ↓ ↓ ↓ 158 | 159 | import { injectIntl } from 'react-intl'; 160 | intl.formatMessage({ 161 | defaultMessage: "custom prop", 162 | "id": "src.__fixtures__.2983810267", 163 | other: 123 164 | }); 165 | 166 | `; 167 | 168 | exports[`filebase = true with Injection API HOC imported: with Injection API HOC imported 1`] = ` 169 | 170 | import { injectIntl } from 'react-intl'; 171 | 172 | intl.formatMessage({ defaultMessage: "hello" }); 173 | 174 | ↓ ↓ ↓ ↓ ↓ ↓ 175 | 176 | import { injectIntl } from 'react-intl'; 177 | intl.formatMessage({ 178 | defaultMessage: "hello", 179 | "id": "src.__fixtures__.messages.613153351" 180 | }); 181 | 182 | `; 183 | 184 | exports[`removePrefix = "src" with Injection API HOC imported: with Injection API HOC imported 1`] = ` 185 | 186 | import { injectIntl } from 'react-intl'; 187 | 188 | intl.formatMessage({ defaultMessage: "hello" }); 189 | 190 | ↓ ↓ ↓ ↓ ↓ ↓ 191 | 192 | import { injectIntl } from 'react-intl'; 193 | intl.formatMessage({ 194 | defaultMessage: "hello", 195 | "id": "__fixtures__.613153351" 196 | }); 197 | 198 | `; 199 | 200 | exports[`removePrefix = "src.__fixtures__" with Injection API HOC imported: with Injection API HOC imported 1`] = ` 201 | 202 | import { injectIntl } from 'react-intl'; 203 | 204 | intl.formatMessage({ defaultMessage: "hello" }); 205 | 206 | ↓ ↓ ↓ ↓ ↓ ↓ 207 | 208 | import { injectIntl } from 'react-intl'; 209 | intl.formatMessage({ 210 | defaultMessage: "hello", 211 | "id": "613153351" 212 | }); 213 | 214 | `; 215 | 216 | exports[`removePrefix = "src/" -- with slash with Injection API HOC imported: with Injection API HOC imported 1`] = ` 217 | 218 | import { injectIntl } from 'react-intl'; 219 | 220 | intl.formatMessage({ defaultMessage: "hello" }); 221 | 222 | ↓ ↓ ↓ ↓ ↓ ↓ 223 | 224 | import { injectIntl } from 'react-intl'; 225 | intl.formatMessage({ 226 | defaultMessage: "hello", 227 | "id": "__fixtures__.613153351" 228 | }); 229 | 230 | `; 231 | 232 | exports[`removePrefix = /__fixtures__/ with Injection API HOC imported: with Injection API HOC imported 1`] = ` 233 | 234 | import { injectIntl } from 'react-intl'; 235 | 236 | intl.formatMessage({ defaultMessage: "hello" }); 237 | 238 | ↓ ↓ ↓ ↓ ↓ ↓ 239 | 240 | import { injectIntl } from 'react-intl'; 241 | intl.formatMessage({ 242 | defaultMessage: "hello", 243 | "id": "src.613153351" 244 | }); 245 | 246 | `; 247 | 248 | exports[`removePrefix = false with Injection API HOC imported: with Injection API HOC imported 1`] = ` 249 | 250 | import { injectIntl } from 'react-intl'; 251 | 252 | intl.formatMessage({ defaultMessage: "hello" }); 253 | 254 | ↓ ↓ ↓ ↓ ↓ ↓ 255 | 256 | import { injectIntl } from 'react-intl'; 257 | intl.formatMessage({ 258 | defaultMessage: "hello", 259 | "id": "src.__fixtures__.613153351" 260 | }); 261 | 262 | `; 263 | -------------------------------------------------------------------------------- /src/__tests__/components.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { cases } from '../utils/testUtils' 3 | 4 | const filename = path.resolve(__dirname, '..', '__fixtures__', 'messages.js') 5 | 6 | const defaultTest = { 7 | title: 'default', 8 | code: ` 9 | import { FormattedMessage } from 'react-intl'; 10 | 11 | ; 12 | `, 13 | } 14 | 15 | const multiUseTest = { 16 | title: 'multiple uses', 17 | code: ` 18 | import { FormattedMessage } from 'react-intl'; 19 | 20 | ; 21 | ; 22 | `, 23 | } 24 | 25 | const allSupportedComponentsTest = { 26 | title: 'import all supported components', 27 | code: ` 28 | import { FormattedHTMLMessage, FormattedMessage } from 'react-intl'; 29 | 30 | ; 31 | ; 32 | `, 33 | } 34 | 35 | const withValueInMessageTest = { 36 | title: 'with a value interpolated in the message', 37 | code: ` 38 | import { FormattedMessage } from 'react-intl'; 39 | 40 | ; 41 | `, 42 | } 43 | 44 | const withVariableMessageTest = { 45 | title: 'with a variable as the defaultMessage', 46 | code: ` 47 | import { FormattedMessage } from 'react-intl'; 48 | 49 | const message = "variable message"; 50 | 51 | ; 52 | `, 53 | } 54 | 55 | const importAsTest = { 56 | title: 'with FormattedMessage imported as something else', 57 | code: ` 58 | import { FormattedMessage as T } from 'react-intl'; 59 | 60 | ; 61 | `, 62 | } 63 | 64 | const nestedJSXTest = { 65 | title: 'with FormattedMessage nested in other JSX', 66 | code: ` 67 | import { FormattedMessage } from 'react-intl'; 68 | 69 |
70 | 71 |
72 | `, 73 | } 74 | 75 | const throwWhenNotAnalyzableTest = { 76 | title: 'throws if defaultMessage isn’t analyzable', 77 | code: ` 78 | import { FormattedMessage } from 'react-intl'; 79 | 80 | const getMsg = () => 'hello'; 81 | 82 | ; 83 | `, 84 | error: /\[React Intl Auto\] defaultMessage must be statically evaluate-able for extraction/u, 85 | snapshot: false, 86 | } 87 | 88 | const notTransformIfNotImportedTest = { 89 | title: 'does nothing if components not imported from react-intl', 90 | snapshot: false, 91 | code: ` 92 | import any from 'any-module'; 93 | ; 94 | `, 95 | } 96 | 97 | const notTransformIfSpreadAttributeTest = { 98 | title: 'does nothing if component props are spread', 99 | snapshot: false, 100 | code: ` 101 | import { FormattedMessage } from 'react-intl'; 102 | const props = { 103 | defaultMessage: 'hello' 104 | }; 105 | ; 106 | `, 107 | } 108 | 109 | const keyTest = { 110 | title: 'using key', 111 | code: ` 112 | import { FormattedMessage } from 'react-intl'; 113 | 114 | ; 115 | `, 116 | } 117 | 118 | const tests = [ 119 | defaultTest, 120 | multiUseTest, 121 | allSupportedComponentsTest, 122 | withValueInMessageTest, 123 | withVariableMessageTest, 124 | importAsTest, 125 | nestedJSXTest, 126 | throwWhenNotAnalyzableTest, 127 | notTransformIfNotImportedTest, 128 | notTransformIfSpreadAttributeTest, 129 | keyTest, 130 | ] 131 | 132 | cases(filename, [ 133 | { title: 'default', tests }, 134 | { 135 | title: 'useKey = true', 136 | tests: [defaultTest, keyTest], 137 | pluginOptions: { useKey: true }, 138 | }, 139 | 140 | { 141 | title: 'removePrefix = "src"', 142 | tests: [defaultTest], 143 | pluginOptions: { removePrefix: 'src' }, 144 | }, 145 | 146 | { 147 | title: 'removePrefix = "src/" -- with slash', 148 | tests: [defaultTest], 149 | pluginOptions: { removePrefix: 'src/' }, 150 | }, 151 | 152 | { 153 | title: 'filebase = true', 154 | tests: [defaultTest], 155 | pluginOptions: { filebase: true }, 156 | }, 157 | 158 | { 159 | title: 'includeExportName = true', 160 | tests: [defaultTest], 161 | pluginOptions: { includeExportName: true }, 162 | }, 163 | 164 | { 165 | title: 'includeExportName = all', 166 | tests: [defaultTest], 167 | pluginOptions: { includeExportName: 'all' }, 168 | }, 169 | 170 | { 171 | title: 'removePrefix = true, includeExportName = true', 172 | tests: [defaultTest], 173 | pluginOptions: { removePrefix: true, includeExportName: true }, 174 | }, 175 | 176 | { 177 | title: 'removePrefix = false', 178 | tests: [defaultTest], 179 | pluginOptions: { removePrefix: false }, 180 | }, 181 | 182 | { 183 | title: 'removePrefix = true, includeExportName = all', 184 | tests: [defaultTest], 185 | pluginOptions: { removePrefix: true, includeExportName: 'all' }, 186 | }, 187 | 188 | { 189 | title: 'extractComments = false', 190 | tests: [defaultTest], 191 | pluginOptions: { extractComments: false }, 192 | }, 193 | 194 | { 195 | title: 'removePrefix = /__fixtures__/', 196 | tests: [defaultTest], 197 | pluginOptions: { removePrefix: /[\\/]__fixtures__/u }, 198 | }, 199 | 200 | { 201 | title: 'removePrefix = "src.__fixtures__"', 202 | tests: [defaultTest], 203 | pluginOptions: { removePrefix: 'src.__fixtures__' }, 204 | }, 205 | ]) 206 | -------------------------------------------------------------------------------- /src/__tests__/hook.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { cases } from '../utils/testUtils' 3 | 4 | const filename = path.resolve(__dirname, '..', '__fixtures__', 'messages.js') 5 | 6 | const defaultTest = { 7 | title: 'with useIntl hook imported', 8 | code: ` 9 | import { useIntl } from 'react-intl'; 10 | 11 | intl.formatMessage({ defaultMessage: "hello" }); 12 | `, 13 | } 14 | 15 | const multiUseTest = { 16 | title: 'multiple uses', 17 | code: ` 18 | import { useIntl } from 'react-intl'; 19 | 20 | intl.formatMessage({ defaultMessage: "hello" }); 21 | intl.formatMessage({ defaultMessage: "hello" }); 22 | intl.formatMessage({ defaultMessage: "some other message" }); 23 | `, 24 | } 25 | 26 | const withValueInMessageTest = { 27 | title: 'with a value interpolated in the message', 28 | code: ` 29 | import { useIntl } from 'react-intl'; 30 | 31 | intl.formatMessage({ defaultMessage: \`template string ${1 + 1}\` }); 32 | `, 33 | } 34 | 35 | const withVariableMessageTest = { 36 | title: 'with a variable as the defaultMessage', 37 | code: ` 38 | import { useIntl } from 'react-intl'; 39 | 40 | const message = "variable message"; 41 | 42 | intl.formatMessage({ defaultMessage: message }); 43 | `, 44 | } 45 | 46 | const withVariableMessageDescriptor = { 47 | title: 'with a variable as the defaultMessage', 48 | code: ` 49 | import { useIntl } from 'react-intl'; 50 | import { message } from './messages' 51 | 52 | intl.formatMessage(messages); 53 | `, 54 | } 55 | 56 | const withCustomProperties = { 57 | title: 'with custom properties in formatMessage call', 58 | code: ` 59 | import { useIntl } from 'react-intl'; 60 | 61 | intl.formatMessage({ defaultMessage: "custom prop", other: 123 }); 62 | `, 63 | } 64 | 65 | const someSupportedUseCases = { 66 | title: 'some supported use cases', 67 | code: ` 68 | import { useIntl } from 'react-intl'; 69 | 70 | const Component2 = () => { 71 | const intl = useIntl(); 72 | const label = intl.formatMessage({ defaultMessage: "hello" }); 73 | return ( 74 | 77 | ); 78 | }; 79 | `, 80 | } 81 | 82 | const importAsTest = { 83 | title: 'with FormattedMessage imported as something else', 84 | code: ` 85 | import { useIntl as i18n } from 'react-intl'; 86 | 87 | intl.formatMessage({ defaultMessage: "i18n" }); 88 | `, 89 | } 90 | 91 | const throwWhenNotAnalyzableTest = { 92 | title: 'throws if defaultMessage isn’t analyzable', 93 | code: ` 94 | import { useIntl } from 'react-intl'; 95 | const getMsg = () => 'hello'; 96 | intl.formatMessage({ 97 | defaultMessage: getMsg(), 98 | }); 99 | `, 100 | error: /\[React Intl Auto\] defaultMessage must be statically evaluate-able for extraction/u, 101 | snapshot: false, 102 | } 103 | 104 | const notTransformIfNotImportedTest = { 105 | title: 'does nothing if react-intl is not imported', 106 | snapshot: false, 107 | code: ` 108 | import any from 'any-module'; 109 | intl.formatMessage({ 110 | defaultMessage: "hello" 111 | }); 112 | `, 113 | } 114 | 115 | const notTransformIfIdIsProvided = { 116 | title: 'does nothing if id is already provided', 117 | snapshot: false, 118 | code: ` 119 | import { useIntl } from 'react-intl'; 120 | intl.formatMessage({ 121 | id: "my.custom.id", 122 | defaultMessage: "hello" 123 | }); 124 | `, 125 | } 126 | 127 | const injectIntlWithProps = { 128 | title: 'with injectIntl', 129 | code: ` 130 | import { injectIntl } from 'react-intl'; 131 | function App({ intl }) { 132 | return
{intl.formatMessage({ defaultMessage: 'hello' })}
133 | } 134 | 135 | export default injectIntl(App) 136 | `, 137 | } 138 | 139 | const withKeyFlag = { 140 | title: 'withKeyFlag', 141 | code: ` 142 | import { useIntl } from 'react-intl'; 143 | intl.formatMessage({ 144 | key: 'foobar', 145 | defaultMessage: 'hello' 146 | }); 147 | `, 148 | } 149 | 150 | const tests = [ 151 | defaultTest, 152 | multiUseTest, 153 | withValueInMessageTest, 154 | withVariableMessageTest, 155 | withVariableMessageDescriptor, 156 | withCustomProperties, 157 | someSupportedUseCases, 158 | importAsTest, 159 | throwWhenNotAnalyzableTest, 160 | notTransformIfNotImportedTest, 161 | notTransformIfIdIsProvided, 162 | injectIntlWithProps, 163 | withKeyFlag, 164 | ] 165 | 166 | cases(filename, [ 167 | { title: 'default', tests }, 168 | { 169 | title: 'removePrefix = "src"', 170 | tests: [defaultTest], 171 | pluginOptions: { removePrefix: 'src' }, 172 | }, 173 | 174 | { 175 | title: 'removePrefix = "src/" -- with slash', 176 | tests: [defaultTest], 177 | pluginOptions: { removePrefix: 'src/' }, 178 | }, 179 | 180 | { 181 | title: 'filebase = true', 182 | tests: [defaultTest], 183 | pluginOptions: { filebase: true }, 184 | }, 185 | 186 | // { 187 | // title: 'includeExportName = true', 188 | // tests: [defaultTest], 189 | // pluginOptions: { includeExportName: true }, 190 | // }, 191 | // { 192 | // title: 'includeExportName = all', 193 | // tests: [defaultTest], 194 | // pluginOptions: { includeExportName: 'all' }, 195 | // }, 196 | // { 197 | // title: 'removePrefix = true, includeExportName = true', 198 | // tests: [defaultTest], 199 | // pluginOptions: { removePrefix: true, includeExportName: true }, 200 | // }, 201 | { 202 | title: 'removePrefix = false', 203 | tests: [defaultTest], 204 | pluginOptions: { removePrefix: false }, 205 | }, 206 | 207 | // { 208 | // title: 'removePrefix = true, includeExportName = all', 209 | // tests: [defaultTest], 210 | // pluginOptions: { removePrefix: true, includeExportName: 'all' }, 211 | // }, 212 | // { 213 | // title: 'extractComments = false', 214 | // tests: [defaultTest], 215 | // pluginOptions: { extractComments: false }, 216 | // }, 217 | { 218 | title: 'removePrefix = /__fixtures__/', 219 | tests: [defaultTest], 220 | pluginOptions: { removePrefix: /[\\/]__fixtures__/u }, 221 | }, 222 | 223 | { 224 | title: 'removePrefix = "src.__fixtures__"', 225 | tests: [defaultTest], 226 | pluginOptions: { removePrefix: 'src.__fixtures__' }, 227 | }, 228 | { 229 | title: 'useKey = true', 230 | tests: [defaultTest, withKeyFlag], 231 | pluginOptions: { useKey: true }, 232 | }, 233 | ]) 234 | -------------------------------------------------------------------------------- /src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { cases } from '../utils/testUtils' 3 | 4 | const filename = path.resolve(__dirname, '..', '__fixtures__', 'messages.js') 5 | 6 | const defaultTest = { 7 | title: 'default', 8 | code: ` 9 | import { defineMessages } from 'react-intl' 10 | 11 | export default defineMessages({ 12 | hello: 'hello', 13 | }) 14 | `, 15 | } 16 | 17 | const multiExportTest = { 18 | title: 'multi export', 19 | code: ` 20 | import { defineMessages } from 'react-intl' 21 | 22 | export const extra = defineMessages({ 23 | hello: 'hello world extra' 24 | }) 25 | 26 | export default defineMessages({ 27 | hello: 'hello world', 28 | }) 29 | `, 30 | } 31 | 32 | const leadingCommentTest = { 33 | title: 'leading comment', 34 | code: ` 35 | import { defineMessages } from 'react-intl' 36 | 37 | export default defineMessages({ 38 | // The main Hello of our app. 39 | hello: 'hello', 40 | 41 | // Another Hello, 42 | // multiline this time 43 | world: { 44 | id: 'hello.world', 45 | defaultMessage: 'hello world', 46 | } 47 | }) 48 | `, 49 | } 50 | 51 | const leadingCommentWithDescriptionTest = { 52 | title: 'leading comment with description', 53 | code: ` 54 | import { defineMessages } from 'react-intl' 55 | 56 | export default defineMessages({ 57 | 58 | // This comment should not be used 59 | world: { 60 | defaultMessage: 'hello world', 61 | description: 'The hello world', 62 | } 63 | }) 64 | `, 65 | } 66 | 67 | const tests = [ 68 | defaultTest, 69 | { 70 | title: 'with include value', 71 | code: ` 72 | import { defineMessages } from 'react-intl' 73 | 74 | defineMessages({ 75 | hello: \`hello world \${1}\`, 76 | }) 77 | `, 78 | }, 79 | 80 | { 81 | title: 'string literal', 82 | code: ` 83 | import { defineMessages } from 'react-intl' 84 | 85 | defineMessages({ 86 | 'hello': 'hello world' 87 | }) 88 | `, 89 | }, 90 | 91 | { 92 | title: 'Object', 93 | code: ` 94 | import { defineMessages } from 'react-intl' 95 | 96 | defineMessages({ 97 | new: { 98 | id: 'this is id', 99 | defaultMessage: 'id', 100 | }, 101 | world: { 102 | defaultMessage: 'world', 103 | }, 104 | headerTitle: { 105 | defaultMessage: 'Welcome to dashboard {name}!', 106 | description: 'Message to greet the user.', 107 | }, 108 | }) 109 | `, 110 | }, 111 | 112 | { 113 | title: 'import as', 114 | code: ` 115 | import { defineMessages as m } from 'react-intl' 116 | 117 | m({ 118 | hello: 'hello' 119 | }) 120 | 121 | `, 122 | }, 123 | 124 | { 125 | title: 'with other func', 126 | code: ` 127 | import { defineMessages } from 'react-intl' 128 | 129 | defineMessages({ 130 | hello: 'hello', 131 | }) 132 | 133 | hello({ 134 | id: 'hoge', 135 | }) 136 | `, 137 | }, 138 | 139 | multiExportTest, 140 | { 141 | title: 'throw error when key is NumiricLiteral', 142 | code: ` 143 | import { defineMessages } from 'react-intl' 144 | 145 | export default defineMessages({ 146 | 1: 'hello', 147 | }) 148 | `, 149 | error: /requires Object key or string literal/u, 150 | snapshot: false, 151 | }, 152 | 153 | { 154 | title: 'not transform if defineMessages is not imported', 155 | code: ` 156 | import any from 'any-module' 157 | 158 | export default defineMessages({ 159 | hello: 'hello' 160 | }) 161 | `, 162 | }, 163 | 164 | { 165 | title: 'not transform when defineMessages argumens is not object', 166 | code: ` 167 | import { defineMessages } from 'react-intl' 168 | 169 | export default defineMessages(1) 170 | `, 171 | }, 172 | 173 | { 174 | title: 'when using the variable', 175 | code: ` 176 | import { defineMessages } from 'react-intl' 177 | 178 | const messages = {hello: 'hello'} 179 | 180 | export default defineMessages(messages) 181 | `, 182 | }, 183 | 184 | { 185 | title: 'not transfrom when the variable can not be found', 186 | code: ` 187 | import { defineMessages } from 'react-intl' 188 | 189 | export default defineMessages(messages) 190 | `, 191 | }, 192 | 193 | { 194 | title: 'not transform when defineMessages argumens is empty', 195 | code: ` 196 | import { defineMessages } from 'react-intl' 197 | 198 | export default defineMessages() 199 | `, 200 | }, 201 | 202 | { 203 | title: 'not transform if callee is not identifier', 204 | code: ` 205 | import { defineMessages } from 'react-intl' 206 | 207 | const m = [defineMessages] 208 | 209 | export default m[0]({ 210 | hello: 'hello world' 211 | }) 212 | `, 213 | }, 214 | 215 | { 216 | title: 'with other specifier', 217 | code: ` 218 | import { defineMessages, FormattedMessage } from 'react-intl' 219 | 220 | export default defineMessages({ 221 | hello: 'hello world', 222 | }) 223 | `, 224 | }, 225 | 226 | leadingCommentTest, 227 | leadingCommentWithDescriptionTest, 228 | { 229 | title: 'eval string', 230 | code: ` 231 | import { defineMessages } from 'react-intl' 232 | 233 | export default defineMessages({ 234 | hello: 'hello' + 'world', 235 | }) 236 | `, 237 | }, 238 | ] 239 | 240 | const moduleSourceNameTest = { 241 | title: 'moduleSourceName', 242 | code: ` 243 | import { defineMessages } from 'gatsby-plugin-intl' 244 | 245 | export default defineMessages({ 246 | hello: 'hello', 247 | }) 248 | `, 249 | } 250 | 251 | cases(filename, [ 252 | { title: 'default', tests }, 253 | { 254 | title: 'removePrefix = "src"', 255 | tests: [defaultTest], 256 | pluginOptions: { removePrefix: 'src' }, 257 | }, 258 | 259 | { 260 | title: 'removePrefix = "src/" -- with slash', 261 | tests: [defaultTest], 262 | pluginOptions: { removePrefix: 'src/' }, 263 | }, 264 | 265 | { 266 | title: 'filebase = true', 267 | tests: [defaultTest], 268 | pluginOptions: { filebase: true }, 269 | }, 270 | 271 | { 272 | title: 'includeExportName = true', 273 | tests: [defaultTest, multiExportTest], 274 | pluginOptions: { includeExportName: true }, 275 | }, 276 | 277 | { 278 | title: 'includeExportName = all', 279 | tests: [defaultTest, multiExportTest], 280 | pluginOptions: { includeExportName: 'all' }, 281 | }, 282 | 283 | { 284 | title: 'removePrefix = true, includeExportName = true', 285 | tests: [defaultTest, multiExportTest], 286 | pluginOptions: { removePrefix: true, includeExportName: true }, 287 | }, 288 | 289 | { 290 | title: 'removePrefix = false', 291 | tests: [defaultTest, multiExportTest], 292 | pluginOptions: { removePrefix: false }, 293 | }, 294 | 295 | { 296 | title: 'removePrefix = true, includeExportName = all', 297 | tests: [defaultTest, multiExportTest], 298 | pluginOptions: { removePrefix: true, includeExportName: 'all' }, 299 | }, 300 | 301 | { 302 | title: 'extractComments = false', 303 | tests: [defaultTest, leadingCommentTest, leadingCommentWithDescriptionTest], 304 | pluginOptions: { extractComments: false }, 305 | }, 306 | 307 | { 308 | title: 'removePrefix = /__fixtures__/', 309 | tests: [defaultTest], 310 | pluginOptions: { 311 | removePrefix: /src[\\/]__f.+?_/u, 312 | includeExportName: true, 313 | }, 314 | }, 315 | 316 | { 317 | title: 'removePrefix = "src.__fixtures__"', 318 | tests: [defaultTest], 319 | pluginOptions: { 320 | removePrefix: 'src.__fixtures__', 321 | }, 322 | }, 323 | 324 | { 325 | title: 'removePrefix = "src.__fixtures__", includeExportName = true', 326 | tests: [defaultTest, multiExportTest], 327 | pluginOptions: { 328 | removePrefix: 'src.__fixtures__', 329 | includeExportName: true, 330 | }, 331 | }, 332 | 333 | { 334 | title: 'moduleSourceNameTest', 335 | tests: [defaultTest, moduleSourceNameTest], 336 | pluginOptions: { 337 | moduleSourceName: 'gatsby-plugin-intl', 338 | }, 339 | }, 340 | 341 | { 342 | title: 'separator = ""', 343 | tests: [defaultTest, multiExportTest], 344 | pluginOptions: { 345 | separator: '', 346 | }, 347 | }, 348 | 349 | { 350 | title: 'separator = "_"', 351 | // tests: [defaultTest, multiExportTest], 352 | tests: [defaultTest, multiExportTest], 353 | pluginOptions: { 354 | separator: '_', 355 | }, 356 | }, 357 | 358 | { 359 | title: 'separator = "foo"', 360 | tests: [defaultTest, multiExportTest], 361 | pluginOptions: { 362 | separator: 'foo', 363 | }, 364 | }, 365 | 366 | { 367 | title: 'relativeTo = "../"', 368 | tests: [defaultTest, multiExportTest], 369 | pluginOptions: { 370 | relativeTo: '..', 371 | }, 372 | }, 373 | 374 | { 375 | title: 'relativeTo = ""', 376 | tests: [defaultTest, multiExportTest], 377 | pluginOptions: { 378 | relativeTo: '', 379 | }, 380 | }, 381 | ]) 382 | -------------------------------------------------------------------------------- /src/__tests__/injection.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { cases } from '../utils/testUtils' 3 | 4 | const filename = path.resolve(__dirname, '..', '__fixtures__', 'messages.js') 5 | 6 | const defaultTest = { 7 | title: 'with Injection API HOC imported', 8 | code: ` 9 | import { injectIntl } from 'react-intl'; 10 | 11 | intl.formatMessage({ defaultMessage: "hello" }); 12 | `, 13 | } 14 | 15 | const multiUseTest = { 16 | title: 'multiple uses', 17 | code: ` 18 | import { injectIntl } from 'react-intl'; 19 | 20 | intl.formatMessage({ defaultMessage: "hello" }); 21 | intl.formatMessage({ defaultMessage: "hello" }); 22 | intl.formatMessage({ defaultMessage: "some other message" }); 23 | `, 24 | } 25 | 26 | const withValueInMessageTest = { 27 | title: 'with a value interpolated in the message', 28 | code: ` 29 | import { injectIntl } from 'react-intl'; 30 | 31 | intl.formatMessage({ defaultMessage: \`template string ${1 + 1}\` }); 32 | `, 33 | } 34 | 35 | const withVariableMessageTest = { 36 | title: 'with a variable as the defaultMessage', 37 | code: ` 38 | import { injectIntl } from 'react-intl'; 39 | 40 | const message = "variable message"; 41 | 42 | intl.formatMessage({ defaultMessage: message }); 43 | `, 44 | } 45 | 46 | const withVariableMessageDescriptor = { 47 | title: 'with a variable as the defaultMessage', 48 | code: ` 49 | import { injectIntl } from 'react-intl'; 50 | import { message } from './messages'; 51 | 52 | intl.formatMessage(message); 53 | `, 54 | } 55 | 56 | const withCustomProperties = { 57 | title: 'with custom properties in formatMessage call', 58 | code: ` 59 | import { injectIntl } from 'react-intl'; 60 | 61 | intl.formatMessage({ defaultMessage: "custom prop", other: 123 }); 62 | `, 63 | } 64 | 65 | const someSupportedUseCases = { 66 | title: 'some supported use cases', 67 | code: ` 68 | import { injectIntl } from 'react-intl'; 69 | 70 | const Component2 = ({ intl }) => { 71 | const label = intl.formatMessage({ defaultMessage: "hello" }); 72 | return ( 73 | 76 | ); 77 | }; 78 | injectIntl(Components2); 79 | `, 80 | } 81 | 82 | const importAsTest = { 83 | title: 'with FormattedMessage imported as something else', 84 | code: ` 85 | import { injectIntl as i18n } from 'react-intl'; 86 | 87 | intl.formatMessage({ defaultMessage: "i18n" }); 88 | `, 89 | } 90 | 91 | const throwWhenNotAnalyzableTest = { 92 | title: 'throws if defaultMessage isn’t analyzable', 93 | code: ` 94 | import { injectIntl } from 'react-intl'; 95 | const getMsg = () => 'hello'; 96 | intl.formatMessage({ 97 | defaultMessage: getMsg(), 98 | }); 99 | `, 100 | error: /\[React Intl Auto\] defaultMessage must be statically evaluate-able for extraction/u, 101 | snapshot: false, 102 | } 103 | 104 | const notTransformIfNotImportedTest = { 105 | title: 'does nothing if react-intl is not imported', 106 | snapshot: false, 107 | code: ` 108 | import any from 'any-module'; 109 | intl.formatMessage({ 110 | defaultMessage: "hello" 111 | }); 112 | `, 113 | } 114 | 115 | const notTransformIfIdIsProvided = { 116 | title: 'does nothing if id is already provided', 117 | snapshot: false, 118 | code: ` 119 | import { injectIntl } from 'react-intl'; 120 | intl.formatMessage({ 121 | id: "my.custom.id", 122 | defaultMessage: "hello" 123 | }); 124 | `, 125 | } 126 | 127 | const tests = [ 128 | defaultTest, 129 | multiUseTest, 130 | withValueInMessageTest, 131 | withVariableMessageTest, 132 | withVariableMessageDescriptor, 133 | withCustomProperties, 134 | someSupportedUseCases, 135 | importAsTest, 136 | throwWhenNotAnalyzableTest, 137 | notTransformIfNotImportedTest, 138 | notTransformIfIdIsProvided, 139 | ] 140 | 141 | cases(filename, [ 142 | { title: 'default', tests }, 143 | { 144 | title: 'removePrefix = "src"', 145 | tests: [defaultTest], 146 | pluginOptions: { removePrefix: 'src' }, 147 | }, 148 | 149 | { 150 | title: 'removePrefix = "src/" -- with slash', 151 | tests: [defaultTest], 152 | pluginOptions: { removePrefix: 'src/' }, 153 | }, 154 | 155 | { 156 | title: 'filebase = true', 157 | tests: [defaultTest], 158 | pluginOptions: { filebase: true }, 159 | }, 160 | 161 | // { 162 | // title: 'includeExportName = true', 163 | // tests: [defaultTest], 164 | // pluginOptions: { includeExportName: true }, 165 | // }, 166 | // { 167 | // title: 'includeExportName = all', 168 | // tests: [defaultTest], 169 | // pluginOptions: { includeExportName: 'all' }, 170 | // }, 171 | // { 172 | // title: 'removePrefix = true, includeExportName = true', 173 | // tests: [defaultTest], 174 | // pluginOptions: { removePrefix: true, includeExportName: true }, 175 | // }, 176 | { 177 | title: 'removePrefix = false', 178 | tests: [defaultTest], 179 | pluginOptions: { removePrefix: false }, 180 | }, 181 | 182 | // { 183 | // title: 'removePrefix = true, includeExportName = all', 184 | // tests: [defaultTest], 185 | // pluginOptions: { removePrefix: true, includeExportName: 'all' }, 186 | // }, 187 | // { 188 | // title: 'extractComments = false', 189 | // tests: [defaultTest], 190 | // pluginOptions: { extractComments: false }, 191 | // }, 192 | { 193 | title: 'removePrefix = /__fixtures__/', 194 | tests: [defaultTest], 195 | pluginOptions: { removePrefix: /[\\/]__fixtures__/u }, 196 | }, 197 | 198 | { 199 | title: 'removePrefix = "src.__fixtures__"', 200 | tests: [defaultTest], 201 | pluginOptions: { removePrefix: 'src.__fixtures__' }, 202 | }, 203 | ]) 204 | -------------------------------------------------------------------------------- /src/babel-plugin-tester.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'babel-plugin-tester' { 2 | function tester(args: unknown): unknown 3 | export = tester 4 | } 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginObj } from '@babel/core' 2 | import { State } from './types' 3 | import { visitJSXElement } from './visitors/jsx' 4 | import { addIdToDefineMessage } from './visitors/addIdToDefineMessage' 5 | import { addIdToFormatMessage } from './visitors/addIdToFormatMessage' 6 | 7 | export default function () { 8 | return { 9 | name: 'react-intl-auto', 10 | visitor: { 11 | JSXElement: visitJSXElement, 12 | CallExpression(path, state: State) { 13 | addIdToFormatMessage(path, state) 14 | addIdToDefineMessage(path, state) 15 | }, 16 | }, 17 | } as PluginObj 18 | } 19 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { NodePath } from '@babel/traverse' 2 | 3 | type BabelTransformationFile = { 4 | opts: { 5 | filename: string 6 | babelrc: boolean 7 | configFile: boolean 8 | passPerPreset: boolean 9 | envName: string 10 | cwd: string 11 | root: string 12 | plugins: unknown[] 13 | presets: unknown[] 14 | parserOpts: object 15 | generatorOpts: object 16 | } 17 | declarations: {} 18 | path: NodePath | null 19 | ast: {} 20 | scope: unknown 21 | metadata: {} 22 | code: string 23 | inputMap: object | null 24 | } 25 | 26 | export type Opts = { 27 | removePrefix?: boolean | string | RegExp 28 | filebase?: boolean 29 | includeExportName?: boolean | 'all' 30 | extractComments?: boolean 31 | useKey?: boolean 32 | moduleSourceName?: string 33 | separator?: string 34 | relativeTo?: string 35 | } 36 | 37 | export type State = { 38 | file: BabelTransformationFile 39 | opts: Opts 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/getPrefix.ts: -------------------------------------------------------------------------------- 1 | import { relative, dirname, sep } from 'path' 2 | import { State } from '../types' 3 | import { dotPath } from '.' 4 | 5 | const escapeRegExp = (text: string) => { 6 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/gu, '\\$&') 7 | } 8 | 9 | const dotPathReplace = ( 10 | fomatted: string, 11 | removePrefix: string, 12 | separator?: string 13 | ) => { 14 | const exp = `^${removePrefix.replace(/\//gu, '')}\\${dotPath( 15 | sep, 16 | separator 17 | )}?` 18 | let reg: RegExp 19 | 20 | // certain separators can throw an error and need to be escaped 21 | // e.g. "_" 22 | try { 23 | reg = new RegExp(exp, 'u') 24 | } catch (error) { 25 | reg = new RegExp(escapeRegExp(exp), 'u') 26 | } 27 | 28 | return dotPath(fomatted, separator).replace(reg, '') 29 | } 30 | 31 | export const getPrefix = ( 32 | { 33 | file: { 34 | opts: { filename }, 35 | }, 36 | opts: { removePrefix, filebase = false, separator, relativeTo }, 37 | }: State, 38 | exportName: string | null 39 | ) => { 40 | if (removePrefix === true) { 41 | return exportName === null ? '' : exportName 42 | } 43 | const file = relative(relativeTo || process.cwd(), filename) 44 | const fomatted = filebase ? file.replace(/\..+$/u, '') : dirname(file) 45 | removePrefix = 46 | removePrefix === undefined || removePrefix === false ? '' : removePrefix 47 | 48 | const fixed = 49 | removePrefix instanceof RegExp 50 | ? dotPath(fomatted.replace(removePrefix, ''), separator) 51 | : dotPathReplace(fomatted, removePrefix, separator) 52 | 53 | if (exportName === null) { 54 | return fixed 55 | } 56 | 57 | if (fixed === '') { 58 | return exportName 59 | } 60 | 61 | return `${fixed}.${exportName}` 62 | } 63 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { sep } from 'path' 2 | import * as t from '@babel/types' 3 | import murmur from 'murmurhash3js' 4 | import { NodePath } from '@babel/core' 5 | 6 | const REG = new RegExp(`\\${sep}`, 'gu') 7 | 8 | const isObjectProperties = ( 9 | properties: NodePath[] 10 | ): properties is NodePath[] => 11 | properties.every((p) => p.isObjectProperty()) 12 | 13 | export const createHash = (message: string) => `${murmur.x86.hash32(message)}` 14 | 15 | export const dotPath = (str: string, separator: string | undefined = '.') => 16 | str.replace(REG, separator) 17 | 18 | export const objectProperty = ( 19 | key: string, 20 | value: string | t.StringLiteral | t.TemplateLiteral 21 | ) => { 22 | const valueNode = typeof value === 'string' ? t.stringLiteral(value) : value 23 | return t.objectProperty(t.stringLiteral(key), valueNode) 24 | } 25 | 26 | export function getObjectProperties(path: NodePath) { 27 | if (path.isObjectExpression()) { 28 | const properties = path.get('properties') 29 | if (isObjectProperties(properties)) { 30 | return properties 31 | } 32 | } else if (path.isIdentifier()) { 33 | const binding = path.scope.getBinding(path.node.name) 34 | if (!binding) { 35 | return null 36 | } 37 | const init = binding.path.get('init') 38 | if (!Array.isArray(init) && !init.type) { 39 | return null 40 | } 41 | const properties = binding.path.get('init.properties') 42 | if (Array.isArray(properties) && isObjectProperties(properties)) { 43 | return properties 44 | } 45 | } 46 | return null 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/isImportLocalName.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../types' 2 | 3 | export const isImportLocalName = ( 4 | name: string | null | undefined, 5 | allowedNames: string[], 6 | { file, opts: { moduleSourceName = 'react-intl' } }: State 7 | ) => { 8 | let isImported = false 9 | 10 | if (file && file.path) { 11 | file.path.traverse({ 12 | ImportDeclaration: { 13 | exit(path) { 14 | const specifiers = path.get('specifiers') 15 | isImported = 16 | path.node.source.value.includes(moduleSourceName) && 17 | specifiers.some((specifier) => { 18 | return ( 19 | specifier.isImportSpecifier() && 20 | allowedNames.includes(specifier.node.imported.name) && 21 | (!name || specifier.node.local.name === name) 22 | ) 23 | }) 24 | 25 | if (isImported) { 26 | path.stop() 27 | } 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | return isImported 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/testUtils.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import pluginTester from 'babel-plugin-tester' 3 | import { Opts } from '../types' 4 | import plugin from '..' 5 | 6 | export function cases( 7 | filename: string, 8 | testCases: { 9 | title: string 10 | tests: { title: string; code: string }[] 11 | pluginOptions?: Opts 12 | }[] 13 | ) { 14 | const defaultOpts = { 15 | title: '', 16 | plugin, 17 | snapshot: true, 18 | babelOptions: { filename, parserOpts: { plugins: ['jsx'] } }, 19 | formatResult: (r: any) => r, 20 | tests: [], 21 | } 22 | 23 | for (const testCase of testCases) { 24 | testCase.tests = testCase.tests.map((t) => ({ ...t, title: t.title })) 25 | pluginTester({ ...defaultOpts, ...testCase }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/visitors/addIdToDefineMessage.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { NodePath } from '@babel/core' 3 | import * as t from '@babel/types' 4 | import { State } from '../types' 5 | import { dotPath, objectProperty, getObjectProperties } from '../utils' 6 | import { isImportLocalName } from '../utils/isImportLocalName' 7 | import { getPrefix } from '../utils/getPrefix' 8 | // import blog from 'babel-log' 9 | 10 | const getId = (path: NodePath, prefix: string, separator?: string) => { 11 | let name 12 | 13 | if (path.isStringLiteral()) { 14 | name = path.node.value 15 | } else if (path.isIdentifier()) { 16 | name = path.node.name 17 | } 18 | 19 | if (!name) { 20 | throw new Error(`requires Object key or string literal`) 21 | } 22 | 23 | return dotPath(join(prefix, name), separator) 24 | } 25 | 26 | const getLeadingComment = (prop: NodePath) => { 27 | const commentNodes = prop.node.leadingComments 28 | return commentNodes 29 | ? commentNodes.map((node) => node.value.trim()).join('\n') 30 | : null 31 | } 32 | 33 | // eslint-disable-next-line max-lines-per-function 34 | const replaceProperties = ( 35 | properties: NodePath[], 36 | state: State, 37 | exportName: string | null 38 | ) => { 39 | const prefix = getPrefix(state, exportName) 40 | const { 41 | opts: { separator }, 42 | } = state 43 | 44 | for (const prop of properties) { 45 | const objectValuePath = prop.get('value') 46 | const objectKeyPath = prop.get('key') 47 | if (Array.isArray(objectKeyPath)) { 48 | return 49 | } 50 | 51 | const messageDescriptorProperties: t.ObjectProperty[] = [] 52 | 53 | // { defaultMessage: 'hello', description: 'this is hello' } 54 | if (objectValuePath.isObjectExpression()) { 55 | const objProps = objectValuePath.get('properties') as NodePath< 56 | t.ObjectProperty 57 | >[] 58 | 59 | // { id: 'already has id', defaultMessage: 'hello' } 60 | const isNotHaveId = objProps.every((v) => { 61 | const keyPath = v.get('key') 62 | return !Array.isArray(keyPath) && keyPath.node.name !== 'id' 63 | }) 64 | 65 | if (isNotHaveId) { 66 | const id = getId(objectKeyPath, prefix, separator) 67 | 68 | messageDescriptorProperties.push(objectProperty('id', id)) 69 | } 70 | 71 | messageDescriptorProperties.push(...objProps.map((v) => v.node)) 72 | } else if ( 73 | objectValuePath.isStringLiteral() || 74 | objectValuePath.isTemplateLiteral() 75 | ) { 76 | // 'hello' or `hello ${user}` 77 | const id = getId(objectKeyPath, prefix, separator) 78 | 79 | messageDescriptorProperties.push( 80 | objectProperty('id', id), 81 | objectProperty('defaultMessage', objectValuePath.node) 82 | ) 83 | } else { 84 | const evaluated = prop.get('value').evaluate() 85 | if (evaluated.confident && typeof evaluated.value === 'string') { 86 | const id = dotPath(join(prefix, evaluated.value), separator) 87 | 88 | messageDescriptorProperties.push( 89 | objectProperty('id', id), 90 | objectProperty('defaultMessage', evaluated.value) 91 | ) 92 | } 93 | } 94 | 95 | const { extractComments = true } = state.opts 96 | 97 | if (extractComments) { 98 | const hasDescription = messageDescriptorProperties.find( 99 | (v) => v.key.name === 'description' 100 | ) 101 | 102 | if (!hasDescription) { 103 | const description = getLeadingComment(prop) 104 | 105 | if (description) { 106 | messageDescriptorProperties.push( 107 | objectProperty('description', description) 108 | ) 109 | } 110 | } 111 | } 112 | objectValuePath.replaceWith(t.objectExpression(messageDescriptorProperties)) 113 | } 114 | } 115 | 116 | const getExportName = ( 117 | path: NodePath, 118 | includeExportName: boolean | 'all' | undefined 119 | ): string | null => { 120 | const namedExport = path.findParent((v) => v.isExportNamedDeclaration()) 121 | const defaultExport = path.findParent((v) => v.isExportDefaultDeclaration()) 122 | 123 | if (includeExportName && namedExport) { 124 | const idPath = namedExport.get('declaration.declarations.0.id') as NodePath< 125 | t.Identifier 126 | > 127 | return idPath.node.name 128 | } 129 | 130 | if (includeExportName === 'all' && defaultExport) { 131 | return 'default' 132 | } 133 | 134 | return null 135 | } 136 | 137 | const isDefineMessagesCall = ( 138 | path: NodePath, 139 | state: State 140 | ): boolean => { 141 | /** 142 | Path "Identifier" 143 | name: "defineMessages" 144 | */ 145 | const callee = path.get('callee') 146 | 147 | return ( 148 | callee.isIdentifier() && 149 | isImportLocalName(callee.node.name, ['defineMessages'], state) 150 | ) 151 | } 152 | 153 | export function addIdToDefineMessage( 154 | path: NodePath, 155 | state: State 156 | ) { 157 | if (!isDefineMessagesCall(path, state)) { 158 | return 159 | } 160 | 161 | if (!path.get('arguments.0')) { 162 | return 163 | } 164 | 165 | const argPath = path.get('arguments.0') as NodePath< 166 | t.ObjectExpression | t.NumericLiteral | t.Identifier 167 | > 168 | 169 | const properties = getObjectProperties(argPath) 170 | 171 | if (!properties) { 172 | return 173 | } 174 | 175 | const exportName = getExportName(path, state.opts.includeExportName || false) 176 | replaceProperties(properties, state, exportName) 177 | } 178 | -------------------------------------------------------------------------------- /src/visitors/addIdToFormatMessage.ts: -------------------------------------------------------------------------------- 1 | import { NodePath } from '@babel/core' 2 | import * as t from '@babel/types' 3 | import { State } from '../types' 4 | import { getPrefix } from '../utils/getPrefix' 5 | import { isImportLocalName } from '../utils/isImportLocalName' 6 | import { createHash, objectProperty, getObjectProperties } from '../utils' 7 | // import blog from 'babel-log' 8 | 9 | // check if given path is related to intl.formatMessage call 10 | function isFormatMessageCall(path: NodePath, state: State) { 11 | // injectIntl or useIntl imported 12 | if (!isImportLocalName(null, ['injectIntl', 'useIntl'], state)) { 13 | return false 14 | } 15 | 16 | /* 17 | Path "MemberExpression" 18 | computed: false 19 | object: Node "Identifier" 20 | name: "intl" 21 | property: Node "Identifier" 22 | name: "formatMessage" 23 | */ 24 | const callee = path.get('callee') 25 | if (!callee.isMemberExpression()) { 26 | return false 27 | } 28 | 29 | /* 30 | Path "Identifier" 31 | name: "formatMessage" 32 | */ 33 | const property = callee.get('property') 34 | const isFormatMessage = 35 | !Array.isArray(property) && 36 | property.isIdentifier() && 37 | property.node.name === 'formatMessage' 38 | 39 | /* 40 | Path "Identifier" 41 | name: "intl" 42 | */ 43 | const objectPath = callee.get('object') 44 | const isIntl = 45 | !Array.isArray(objectPath) && 46 | objectPath.isIdentifier() && 47 | objectPath.node.name === 'intl' 48 | 49 | return Boolean(path.get('arguments.0')) && isIntl && isFormatMessage 50 | } 51 | 52 | function findProperty( 53 | properties: NodePath[], 54 | name: string 55 | ): NodePath | undefined { 56 | return properties.find((arg) => { 57 | const keyPath = arg.get('key') 58 | return !Array.isArray(keyPath) && keyPath.node.name === name 59 | }) 60 | } 61 | 62 | function extractKeyValue( 63 | properties: NodePath[] 64 | ): string | null { 65 | const prop = findProperty(properties, 'key') 66 | if (prop) { 67 | const keyPath = prop.get('key') 68 | if (!Array.isArray(keyPath) && keyPath.node.name === 'key') { 69 | const valuePath = prop.get('value') 70 | const value = valuePath.evaluate().value 71 | return value 72 | } 73 | } 74 | return null 75 | } 76 | 77 | // add automatic ID to intl.formatMessage calls 78 | export function addIdToFormatMessage( 79 | path: NodePath, 80 | state: State 81 | ) { 82 | if (!isFormatMessageCall(path, state)) { 83 | // skip path if this is not intl.formatMessage call 84 | return 85 | } 86 | 87 | // intl.formatMessage first argument is the one which we would like to modify 88 | const arg0 = path.get('arguments.0') as NodePath 89 | 90 | const properties = getObjectProperties(arg0) 91 | // blog(properties) 92 | 93 | // at least defaultMessage property is required to do anything useful 94 | if (!properties) { 95 | return 96 | } 97 | 98 | // if "id" property is already added by a developer or by this script just skip this node 99 | if (findProperty(properties, 'id')) { 100 | return 101 | } 102 | 103 | const keyValue = extractKeyValue(properties) 104 | 105 | const defaultMessageProp = findProperty(properties, 'defaultMessage') 106 | if (defaultMessageProp) { 107 | // try to statically evaluate defaultMessage to generate hash 108 | const evaluated = defaultMessageProp.get('value').evaluate() 109 | 110 | if (!evaluated.confident || typeof evaluated.value !== 'string') { 111 | throw defaultMessageProp 112 | .get('value') 113 | .buildCodeFrameError( 114 | '[React Intl Auto] defaultMessage must be statically evaluate-able for extraction.' 115 | ) 116 | } 117 | 118 | const id = getPrefix( 119 | state, 120 | state.opts.useKey && keyValue ? keyValue : createHash(evaluated.value) 121 | ) 122 | 123 | defaultMessageProp.insertAfter(objectProperty('id', id)) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/visitors/jsx.ts: -------------------------------------------------------------------------------- 1 | import { NodePath } from '@babel/core' 2 | import * as t from '@babel/types' 3 | import { State } from '../types' 4 | import { createHash } from '../utils' 5 | import { isImportLocalName } from '../utils/isImportLocalName' 6 | import { getPrefix } from '../utils/getPrefix' 7 | // import blog from 'babel-log' 8 | 9 | // Process react-intl components 10 | const REACT_COMPONENTS = ['FormattedMessage', 'FormattedHTMLMessage'] 11 | 12 | const getElementAttributePaths = ( 13 | elementPath: NodePath 14 | ) => { 15 | if (!elementPath) { 16 | return {} 17 | } 18 | 19 | const attributesPath = elementPath.get('attributes') as NodePath< 20 | t.JSXAttribute 21 | >[] 22 | 23 | const defaultMessagePath = attributesPath.find( 24 | (attrPath) => 25 | attrPath.node.name && attrPath.node.name.name === 'defaultMessage' 26 | ) 27 | 28 | const idPath = attributesPath.find( 29 | (attrPath) => attrPath.node.name && attrPath.node.name.name === 'id' 30 | ) 31 | 32 | const keyPath = attributesPath.find( 33 | (attrPath) => attrPath.node.name && attrPath.node.name.name === 'key' 34 | ) 35 | 36 | return { id: idPath, defaultMessage: defaultMessagePath, key: keyPath } 37 | } 38 | 39 | /** 40 | Path "JSXAttribute" 41 | name: Node "JSXIdentifier" 42 | name: "defaultMessage" 43 | value: Node "StringLiteral" 44 | extra: Object { 45 | "raw": "\"hello\"", 46 | "rawValue": "hello", 47 | } 48 | value: "hello" 49 | */ 50 | const extractFromValuePath = (jsxAttributePath: NodePath) => { 51 | // blog(jsxAttributePath) 52 | const valuePath = jsxAttributePath.get('value') 53 | if (!valuePath) { 54 | return null 55 | } 56 | /** 57 | Path "StringLiteral" 58 | extra: Object { 59 | "raw": "hello", 60 | "rawValue": "hello", 61 | } 62 | value: "hello" 63 | */ 64 | if (valuePath.isStringLiteral()) { 65 | return valuePath.node.value 66 | } 67 | 68 | /** 69 | Path "JSXExpressionContainer" 70 | expression: Node "CallExpression" 71 | arguments: Array [] 72 | callee: Node "Identifier" 73 | name: "getMsg" 74 | */ 75 | if (valuePath.isJSXExpressionContainer()) { 76 | // Evaluate the message expression to see if it yields a string 77 | const evaluated = valuePath.get('expression').evaluate() 78 | /** 79 | Object { 80 | "confident": true, 81 | "deopt": null, 82 | "value": "variable message", 83 | } 84 | */ 85 | if (evaluated.confident && typeof evaluated.value === 'string') { 86 | return evaluated.value 87 | } 88 | throw valuePath.buildCodeFrameError( 89 | `[React Intl Auto] ${ 90 | jsxAttributePath.get('name').node.name 91 | } must be statically evaluate-able for extraction.` 92 | ) 93 | } 94 | 95 | return null 96 | } 97 | 98 | const generateId = ( 99 | defaultMessage: NodePath, 100 | state: State, 101 | key: NodePath | undefined 102 | ) => { 103 | // ID = path to the file + key 104 | let suffix = key && state.opts.useKey ? extractFromValuePath(key) : '' 105 | if (!suffix) { 106 | // ID = path to the file + hash of the defaultMessage 107 | const messageValue = extractFromValuePath(defaultMessage) 108 | if (messageValue) { 109 | suffix = createHash(messageValue) 110 | } 111 | } 112 | 113 | const prefix = getPrefix(state, suffix) 114 | 115 | // Insert an id attribute before the defaultMessage attribute 116 | defaultMessage.insertBefore( 117 | t.jsxAttribute(t.jsxIdentifier('id'), t.stringLiteral(prefix)) 118 | ) 119 | } 120 | 121 | export const visitJSXElement = (path: NodePath, state: State) => { 122 | const jsxOpeningElement = path.get('openingElement') as NodePath< 123 | t.JSXOpeningElement 124 | > 125 | 126 | // Is this a react-intl component? Handles both: 127 | // import { FormattedMessage as T } from 'react-intl' 128 | // import { FormattedMessage } from 'react-intl' 129 | if ( 130 | t.isJSXIdentifier(jsxOpeningElement.node.name) && 131 | isImportLocalName(jsxOpeningElement.node.name.name, REACT_COMPONENTS, state) 132 | ) { 133 | // Get the attributes for the component 134 | const { id, defaultMessage, key } = getElementAttributePaths( 135 | jsxOpeningElement 136 | ) 137 | 138 | // If valid message but missing ID, generate one 139 | if (!id && defaultMessage) { 140 | generateId(defaultMessage, state, state.opts.useKey ? key : undefined) 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@akameco/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | import { MessageDescriptor } from 'react-intl' 2 | 3 | declare module 'react-intl' { 4 | interface ExtractableMessage { 5 | [key: string]: string 6 | } 7 | 8 | export function defineMessages( 9 | messages: T 10 | ): { [K in keyof T]: MessageDescriptor } 11 | } 12 | --------------------------------------------------------------------------------