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

Commit 4f9f8fc

Browse files
committed
Add extractSourceLocation option
This option will provide metadata in the Message Description for the file and location in the file where the message was defined. When enabled and running it on [this file][1] you'll get this result: ```json [ { "id": "app.title", "defaultMessage": "React Intl Translations Example", "file": "src/client/components/app.js", "start": { "line": 23, "column": 20 }, "end": { "line": 26, "column": 22 } }, { "id": "app.locales_menu_heading", "defaultMessage": "Locales:", "file": "src/client/components/app.js", "start": { "line": 32, "column": 20 }, "end": { "line": 35, "column": 22 } } ] ``` Fixes #71 [1]: https://github.com/yahoo/react-intl/blob/25f3f64d86ac86281157117b5bdb9fa30d1d615f/examples/translations/src/client/components/app.js
1 parent e491de9 commit 4f9f8fc

File tree

7 files changed

+119
-9
lines changed

7 files changed

+119
-9
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ The default message descriptors for the app's default language will be extracted
2424
{
2525
"plugins": [
2626
["react-intl", {
27-
"messagesDir": "./build/messages/",
28-
"enforceDescriptions": true
27+
"messagesDir": "./build/messages/"
2928
}]
3029
]
3130
}
@@ -35,7 +34,9 @@ The default message descriptors for the app's default language will be extracted
3534

3635
- **`messagesDir`**: The target location where the plugin will output a `.json` file corresponding to each component from which React Intl messages were extracted. If not provided, the extracted message descriptors will only be accessible via Babel's API.
3736

38-
- **`enforceDescriptions`**: Whether or not message declarations _must_ contain a `description` to provide context to translators. Defaults to: `false`.
37+
- **`enforceDescriptions`**: Whether message declarations _must_ contain a `description` to provide context to translators. Defaults to: `false`.
38+
39+
- **`extractSourceLocation`**: Whether the metadata about the location of the message in the source file should be extracted. If `true`, then `file`, `start`, and `end` fields will exist for each extracted message descriptors. Defaults to `false`.
3940

4041
- **`moduleSourceName`**: The ES6 module source name of the React Intl package. Defaults to: `"react-intl"`, but can be changed to another name/path to React Intl.
4142

scripts/build-fixtures.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const baseDir = p.resolve(`${__dirname}/../test/fixtures`);
77

88
const fixtures = [
99
'defineMessages',
10+
['extractSourceLocation', {
11+
extractSourceLocation: true,
12+
}],
1013
'FormattedHTMLMessage',
1114
'FormattedMessage',
1215
['moduleSourceName', {

src/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export default function () {
107107
}
108108

109109
function storeMessage({id, description, defaultMessage}, path, state) {
110-
const {opts, reactIntl} = state;
110+
const {file, opts, reactIntl} = state;
111111

112112
if (!(id && defaultMessage)) {
113113
throw path.buildCodeFrameError(
@@ -134,7 +134,15 @@ export default function () {
134134
);
135135
}
136136

137-
reactIntl.messages.set(id, {id, description, defaultMessage});
137+
let loc;
138+
if (opts.extractSourceLocation) {
139+
loc = {
140+
file: p.relative(process.cwd(), file.opts.filename),
141+
...path.node.loc,
142+
};
143+
}
144+
145+
reactIntl.messages.set(id, {id, description, defaultMessage, ...loc});
138146
}
139147

140148
function referencesImport(path, mod, importedNames) {
@@ -257,7 +265,7 @@ export default function () {
257265

258266
// Evaluate the Message Descriptor values, then store it.
259267
descriptor = evaluateMessageDescriptor(descriptor);
260-
storeMessage(descriptor, path, state);
268+
storeMessage(descriptor, messageObj, state);
261269
}
262270

263271
if (referencesImport(callee, moduleSourceName, FUNCTION_NAMES)) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React, {Component} from 'react';
2+
import {FormattedMessage} from 'react-intl';
3+
4+
export default class Foo extends Component {
5+
render() {
6+
return (
7+
<FormattedMessage
8+
id='foo.bar.baz'
9+
defaultMessage='Hello World!'
10+
/>
11+
);
12+
}
13+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
7+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8+
9+
var _react = require('react');
10+
11+
var _react2 = _interopRequireDefault(_react);
12+
13+
var _reactIntl = require('react-intl');
14+
15+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16+
17+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
18+
19+
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
20+
21+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
22+
23+
var Foo = function (_Component) {
24+
_inherits(Foo, _Component);
25+
26+
function Foo() {
27+
_classCallCheck(this, Foo);
28+
29+
return _possibleConstructorReturn(this, (Foo.__proto__ || Object.getPrototypeOf(Foo)).apply(this, arguments));
30+
}
31+
32+
_createClass(Foo, [{
33+
key: 'render',
34+
value: function render() {
35+
return _react2.default.createElement(_reactIntl.FormattedMessage, {
36+
id: 'foo.bar.baz',
37+
defaultMessage: 'Hello World!'
38+
});
39+
}
40+
}]);
41+
42+
return Foo;
43+
}(_react.Component);
44+
45+
exports.default = Foo;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"id": "foo.bar.baz",
4+
"defaultMessage": "Hello World!",
5+
"file": "test/fixtures/extractSourceLocation/actual.js",
6+
"start": {
7+
"line": 7,
8+
"column": 12
9+
},
10+
"end": {
11+
"line": 10,
12+
"column": 14
13+
}
14+
}
15+
]

test/index.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const skipTests = [
1212
'.babelrc',
1313
'.DS_Store',
1414
'enforceDescriptions',
15+
'extractSourceLocation',
1516
'moduleSourceName',
1617
'icuSyntax',
1718
];
@@ -49,7 +50,9 @@ describe('options', () => {
4950
const fixtureDir = path.join(fixturesDir, 'enforceDescriptions');
5051

5152
try {
52-
transform(path.join(fixtureDir, 'actual.js'));
53+
transform(path.join(fixtureDir, 'actual.js'), {
54+
enforceDescriptions: true,
55+
});
5356
assert(false);
5457
} catch (e) {
5558
assert(e);
@@ -83,6 +86,30 @@ describe('options', () => {
8386
console.error(e);
8487
assert(false);
8588
}
89+
90+
// Check message output
91+
const expectedMessages = fs.readFileSync(path.join(fixtureDir, 'expected.json'));
92+
const actualMessages = fs.readFileSync(path.join(fixtureDir, 'actual.json'));
93+
assert.equal(trim(actualMessages), trim(expectedMessages));
94+
});
95+
96+
it('respects extractSourceLocation', () => {
97+
const fixtureDir = path.join(fixturesDir, 'extractSourceLocation');
98+
99+
try {
100+
transform(path.join(fixtureDir, 'actual.js'), {
101+
extractSourceLocation: true,
102+
});
103+
assert(true);
104+
} catch (e) {
105+
console.error(e);
106+
assert(false);
107+
}
108+
109+
// Check message output
110+
const expectedMessages = fs.readFileSync(path.join(fixtureDir, 'expected.json'));
111+
const actualMessages = fs.readFileSync(path.join(fixtureDir, 'actual.json'));
112+
assert.equal(trim(actualMessages), trim(expectedMessages));
86113
});
87114
});
88115

@@ -99,13 +126,11 @@ describe('errors', () => {
99126
assert(/Expected .* but "\." found/.test(e.message));
100127
}
101128
});
102-
103129
});
104130

105131

106132
const BASE_OPTIONS = {
107133
messagesDir: baseDir,
108-
enforceDescriptions: true,
109134
};
110135

111136
function transform(filePath, options = {}) {

0 commit comments

Comments
 (0)