Skip to content
This repository was archived by the owner on Jun 8, 2019. It is now read-only.

Commit f883d80

Browse files
committed
Initial drop
0 parents  commit f883d80

File tree

7 files changed

+233
-0
lines changed

7 files changed

+233
-0
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"optional": ["runtime"]
3+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
*.log
3+
lib

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
*.log
3+
src

LICENSE

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Copyright (c) 2015, Yahoo Inc.
2+
3+
Licensed under the terms of the New BSD license. See below for terms.
4+
5+
Redistribution and use of this software in source and binary forms,
6+
with or without modification, are permitted provided that the following
7+
conditions are met:
8+
9+
* Redistributions of source code must retain the above
10+
copyright notice, this list of conditions and the
11+
following disclaimer.
12+
13+
* Redistributions in binary form must reproduce the above
14+
copyright notice, this list of conditions and the
15+
following disclaimer in the documentation and/or other
16+
materials provided with the distribution.
17+
18+
* Neither the name of Yahoo Inc. nor the names of its
19+
contributors may be used to endorse or promote products
20+
derived from this software without specific prior
21+
written permission of Yahoo Inc.
22+
23+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25+
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# babel-plugin-react-intl
2+
3+
Extracts string messages from modules that use react-intl.
4+
5+
## Installation
6+
7+
```sh
8+
$ npm install babel-plugin-react-intl
9+
```
10+
11+
## Usage
12+
13+
### Via `.babelrc` (Recommended)
14+
15+
**.babelrc**
16+
17+
```json
18+
{
19+
"plugins": ["react-intl"]
20+
}
21+
```
22+
23+
### Via CLI
24+
25+
```sh
26+
$ babel --plugins react-intl script.js
27+
```
28+
29+
### Via Node API
30+
31+
```javascript
32+
require("babel-core").transform("code", {
33+
plugins: ["react-intl"]
34+
});
35+
```

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "babel-plugin-react-intl",
3+
"version": "1.0.0",
4+
"description": "Extracts string messages from modules that use react-intl.",
5+
"repository": {
6+
"type": "git",
7+
"url": "git+https://github.com/yahoo/babel-react-intl.git"
8+
},
9+
"license": "BSD-3-Clause",
10+
"main": "lib/index.js",
11+
"author": "Eric Ferraiuolo <[email protected]>",
12+
"devDependencies": {
13+
"babel": "^5.8.21"
14+
},
15+
"scripts": {
16+
"build": "babel-plugin build",
17+
"push": "babel-plugin publish",
18+
"test": "babel-plugin test"
19+
},
20+
"keywords": [
21+
"babel-plugin"
22+
],
23+
"bugs": {
24+
"url": "https://github.com/yahoo/babel-react-intl/issues"
25+
},
26+
"homepage": "https://github.com/yahoo/babel-react-intl#readme",
27+
"dependencies": {
28+
"babel": "^5.8.20",
29+
"babel-runtime": "^5.8.20",
30+
"mkdirp": "^0.5.1"
31+
}
32+
}

src/index.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as path from 'path';
2+
import {readFileSync, writeFileSync} from 'fs';
3+
import {sync as mkdirpSync} from 'mkdirp';
4+
5+
const COMPONENT_NAMES = [
6+
'FormattedMessage',
7+
'FormattedHTMLMessage',
8+
];
9+
10+
const FUNCTION_NAMES = [
11+
'formatMessage',
12+
'formatHTMLMessage',
13+
];
14+
15+
const IMPORTED_NAMES = new Set([...COMPONENT_NAMES, ...FUNCTION_NAMES]);
16+
const ATTR_WHITELIST = new Set(['id', 'description', 'defaultMessage']);
17+
18+
export default function ({Plugin, types: t}) {
19+
function referencesImport(node, mod, importedNames) {
20+
if (!(t.isIdentifier(node) || t.isJSXIdentifier(node))) {
21+
return false;
22+
}
23+
24+
return importedNames.some((name) => node.referencesImport(mod, name));
25+
}
26+
27+
function checkMessageId(messages, message, node, file) {
28+
if (!message.id) {
29+
throw file.errorWithNode(node,
30+
'React Intl message is missing an `id`.'
31+
);
32+
}
33+
34+
if (messages.hasOwnProperty(message.id)) {
35+
throw file.errorWithNode(node,
36+
`Duplicate React Intl message id: "${message.id}"`
37+
);
38+
}
39+
}
40+
41+
return new Plugin('react-intl', {
42+
visitor: {
43+
Program: {
44+
enter(node, parent, scope, file) {
45+
const {moduleSourceName} = file.opts.extra.reactIntl;
46+
const {imports} = file.metadata.modules;
47+
48+
let hasReactIntlMessages = imports.some((mod) => {
49+
if (mod.source === moduleSourceName) {
50+
return mod.imported.some((name) => {
51+
return IMPORTED_NAMES.has(name);
52+
});
53+
}
54+
});
55+
56+
if (hasReactIntlMessages) {
57+
file.reactIntl = {
58+
messages: {}
59+
};
60+
} else {
61+
this.skip();
62+
}
63+
},
64+
65+
exit(node, parent, scope, file) {
66+
const {basename, filename} = file.opts;
67+
const {messagesDir} = file.opts.extra.reactIntl;
68+
69+
let messagesFilename = path.join(
70+
messagesDir, path.dirname(filename), basename + '.json'
71+
);
72+
73+
let {messages} = file.reactIntl;
74+
let messagesFile = JSON.stringify(messages, null, 2);
75+
76+
mkdirpSync(path.dirname(messagesFilename));
77+
writeFileSync(messagesFilename, messagesFile);
78+
}
79+
},
80+
81+
JSXOpeningElement(node, parent, scope, file) {
82+
const {moduleSourceName} = file.opts.extra.reactIntl;
83+
let name = this.get('name');
84+
85+
if (referencesImport(name, moduleSourceName, COMPONENT_NAMES)) {
86+
let message = node.attributes
87+
.filter((attr) => ATTR_WHITELIST.has(attr.name.name))
88+
.reduce((message, attr) => {
89+
message[attr.name.name] = attr.value.value;
90+
return message;
91+
}, {});
92+
93+
let {messages} = file.reactIntl;
94+
95+
checkMessageId(messages, message, node, file);
96+
Object.assign(messages, {[message.id]: message});
97+
}
98+
},
99+
100+
CallExpression(node, parent, scope, file) {
101+
const {moduleSourceName} = file.opts.extra.reactIntl;
102+
103+
let callee = this.get('callee');
104+
let messageArg = node.arguments[1];
105+
106+
if (referencesImport(callee, moduleSourceName, FUNCTION_NAMES) &&
107+
t.isObjectExpression(messageArg)) {
108+
109+
let message = messageArg.properties
110+
.filter((prop) => ATTR_WHITELIST.has(prop.key.name))
111+
.reduce((message, prop) => {
112+
message[prop.key.name] = prop.value.value;
113+
return message;
114+
}, {});
115+
116+
let {messages} = file.reactIntl;
117+
118+
checkMessageId(messages, message, node, file);
119+
Object.assign(messages, {[message.id]: message});
120+
}
121+
}
122+
}
123+
});
124+
}

0 commit comments

Comments
 (0)