UNPKG

3.58 kBJavaScriptView Raw
1import fs from 'fs';
2
3import {isElementMarker, isFunctionMarker} from './ast';
4import {assertInput} from './errors';
5import generate from './generation';
6import io from './io';
7import {options} from './options';
8import parsing from './parsing';
9import {
10 validateFunctionMessage,
11 validateAndSanitizeElement
12} from './validation';
13
14
15export function extractFunctionMessage(callExpression) {
16 return callExpression.arguments[0].value;
17}
18
19
20export function extractElementMessage(jsxElementPath) {
21 validateAndSanitizeElement(jsxElementPath);
22 const messageWithContainer = generate(jsxElementPath.node);
23 // TODO: is there a better way to handle whitespace of jsxChildren ?
24 // thought: possibly by re-situating them as the children of a Block?
25 const extractRe = new RegExp(
26 `<${options.elementMarker}>([\\s\\S]+?)<\\/${options.elementMarker}>`
27 );
28 return extractRe.exec(messageWithContainer)[1].trim();
29}
30
31
32// TODO: is there a more elegant approach?
33export function extractElementMessageWithoutSideEffects(jsxElement) {
34 assertInput(
35 isElementMarker(jsxElement),
36 "Attempted to extract message from non-marker",
37 jsxElement
38 );
39 return Object.keys(extractFromSource(generate(jsxElement)))[0];
40}
41
42
43function messageReferenceForNode(node, sourceFile) {
44 return {
45 sourceFile,
46 line: node.loc.start.line,
47 node
48 };
49}
50
51
52export function extractFromSource(src, filePath = '<Program>') {
53 const messages = {};
54
55 function addMessage(node, message) {
56 let item = messages[message];
57 if (item) {
58 item.push(messageReferenceForNode(node, filePath));
59 } else {
60 messages[message] = [messageReferenceForNode(node, filePath)];
61 }
62 }
63
64 const plugin = function({types: t}, {opts} = {opts: {}}) {
65 Object.assign(options, opts);
66 return {
67 visitor: {
68 CallExpression({node: callExpression}) {
69 if (isFunctionMarker(callExpression)) {
70 validateFunctionMessage(callExpression);
71 addMessage(callExpression, extractFunctionMessage(callExpression));
72 }
73 },
74
75 JSXElement(path) {
76 if (isElementMarker(path.node)) {
77 addMessage(path.node, extractElementMessage(path));
78 }
79 },
80 }
81 };
82 };
83
84 parsing.transform(src, plugin);
85 return messages;
86}
87
88
89export function extractMessages(src) {
90 return Object.keys(extractFromSource(src));
91}
92
93
94export default function extractFromPaths(paths, {outputFormat = 'po', ...options} = {}) {
95 let messages = {};
96 const ffmp = require('../bin/filesFromMixedPaths');
97
98 function addItem(message, item) {
99 let existing = messages[message];
100 if (existing) {
101 messages[message] = existing.concat(item);
102 } else {
103 messages[message] = item;
104 }
105 }
106
107 ffmp(paths).forEach(path => {
108 const src = fs.readFileSync(path, "utf-8");
109 try {
110 Object.entries(extractFromSource(src, path)).forEach(
111 ([msg, item]) => addItem(msg, item)
112 );
113 } catch(e) {
114 console.error(path, e);
115 if (e.stack) {
116 console.error(e.stack);
117 }
118 if (e.node) {
119 console.error("Error at", e.node.loc, generate(e.node));
120 }
121 }
122 });
123
124 return io[outputFormat].out(messages, options);
125}