diff --git a/package.json b/package.json index cad288a..6713e98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-jsx-i18n", - "version": "0.6.0", + "version": "0.6.1", "description": "Provides gettext-enhanced React components, a babel plugin for extracting the strings and a script to compile translated strings to a format usable by the components.", "main": "client/index.js", "types": "client/index.d.ts", diff --git a/src/tools/cli.js b/src/tools/cli.js index 88aa817..e107d2d 100644 --- a/src/tools/cli.js +++ b/src/tools/cli.js @@ -45,10 +45,16 @@ yargs coerce: val => val.split(','), describe: 'file extensions to consider', }); + y.option('base', { + alias: 'b', + default: null, + type: 'string', + describe: 'base dir to generate relative file paths; default to current dir', + }); }, argv => { const files = flattenPaths(argv.paths, argv.ext); - const {pot, errors} = extractFromFiles(files); + const {pot, errors} = extractFromFiles(files, argv.base || process.cwd()); if (errors) { errors.forEach(err => console.error(err)); process.exit(1); diff --git a/src/tools/extract-plugin.js b/src/tools/extract-plugin.js index 886796e..a477874 100644 --- a/src/tools/extract-plugin.js +++ b/src/tools/extract-plugin.js @@ -99,8 +99,8 @@ const processElement = (path, types, allowParam = false) => { return string; }; -const getLocation = (path, state) => { - const filename = relative(process.cwd(), state.file.opts.filename); +const getLocation = (base, path, state) => { + const filename = relative(base, state.file.opts.filename); return `${filename}:${path.node.loc.start.line}`; }; @@ -110,16 +110,16 @@ const getContext = path => { return contextAttr ? contextAttr.value.value : undefined; }; -const processTranslate = (path, state, types) => { +const processTranslate = (base, path, state, types) => { const translatableString = processElement(path, types, true); return { msgid: translatableString, msgctxt: getContext(path), - reference: getLocation(path, state), + reference: getLocation(base, path, state), }; }; -const processTranslateString = (path, state, funcName, types) => { +const processTranslateString = (base, path, state, funcName, types) => { const args = path.node.arguments; if (args.length === 0) { throw path.buildCodeFrameError('Translate.string() called with no arguments'); @@ -131,11 +131,11 @@ const processTranslateString = (path, state, funcName, types) => { return { msgid, msgctxt, - reference: getLocation(path, state), + reference: getLocation(base, path, state), }; }; -const processPluralTranslate = (path, state, types) => { +const processPluralTranslate = (base, path, state, types) => { let singularPath, pluralPath; path .get('children') @@ -167,11 +167,11 @@ const processPluralTranslate = (path, state, types) => { msgid: processElement(singularPath, types, true), msgid_plural: processElement(pluralPath, types, true), msgctxt: getContext(path), - reference: getLocation(path, state), + reference: getLocation(base, path, state), }; }; -const processPluralTranslateString = (path, state, funcName, types) => { +const processPluralTranslateString = (base, path, state, funcName, types) => { const args = path.node.arguments; if (args.length < 2) { throw path.buildCodeFrameError('PluralTranslate.string() called with less than 2 arguments'); @@ -186,11 +186,11 @@ const processPluralTranslateString = (path, state, funcName, types) => { msgid, msgid_plural, msgctxt, - reference: getLocation(path, state), + reference: getLocation(base, path, state), }; }; -const makeI18nPlugin = () => { +const makeI18nPlugin = base => { const entries = []; const i18nPlugin = ({types}) => { return { @@ -198,9 +198,9 @@ const makeI18nPlugin = () => { JSXElement(path, state) { const elementName = path.node.openingElement.name.name; if (elementName === 'Translate') { - entries.push(processTranslate(path, state, types)); + entries.push(processTranslate(base, path, state, types)); } else if (elementName === 'PluralTranslate') { - entries.push(processPluralTranslate(path, state, types)); + entries.push(processPluralTranslate(base, path, state, types)); } }, CallExpression(path, state) { @@ -224,9 +224,9 @@ const makeI18nPlugin = () => { // we got a proper call of one of our translation functions const qualifiedFuncName = `${elementName}.${funcName}`; if (elementName === 'Translate') { - entries.push(processTranslateString(path, state, qualifiedFuncName, types)); + entries.push(processTranslateString(base, path, state, qualifiedFuncName, types)); } else if (elementName === 'PluralTranslate') { - entries.push(processPluralTranslateString(path, state, qualifiedFuncName, types)); + entries.push(processPluralTranslateString(base, path, state, qualifiedFuncName, types)); } }, }, diff --git a/src/tools/extract.js b/src/tools/extract.js index e06de39..6dbcf7e 100644 --- a/src/tools/extract.js +++ b/src/tools/extract.js @@ -6,9 +6,9 @@ import moment from 'moment-timezone'; import {mergeEntries} from 'babel-plugin-extract-text/src/builders'; import makeI18nPlugin from './extract-plugin'; -const extractFromFiles = (files, headers = undefined, highlightErrors = true) => { +const extractFromFiles = (files, base, headers = undefined, highlightErrors = true) => { const errors = []; - const {i18nPlugin, entries} = makeI18nPlugin(); + const {i18nPlugin, entries} = makeI18nPlugin(base); files.forEach(file => { try { diff --git a/tests/__snapshots__/extract.test.js.snap b/tests/__snapshots__/extract.test.js.snap index 8ab0d92..f50d58a 100644 --- a/tests/__snapshots__/extract.test.js.snap +++ b/tests/__snapshots__/extract.test.js.snap @@ -511,6 +511,147 @@ msgstr \\"\\"", } `; +exports[`Messages are properly extracted 3`] = ` +Object { + "pot": "msgid \\"\\" +msgstr \\"\\" +\\"POT-Creation-Date: 2018-04-18 22:20+0000\\\\n\\" +\\"Content-Type: text/plain; charset=utf-8\\\\n\\" +\\"Content-Transfer-Encoding: 8bit\\\\n\\" +\\"MIME-Version: 1.0\\\\n\\" +\\"Generated-By: react-jsx-i18n-extract\\\\n\\" + +#: example.jsx:37 +#: example.jsx:38 +#: example.jsx:86 +msgid \\"\\\\\\"Rats.\\\\\\"\\" +msgstr \\"\\" + +#: example.jsx:39 +#: example.jsx:40 +msgid \\"foobar\\" +msgstr \\"\\" + +#: example.jsx:42 +msgid \\"You are ugly!\\" +msgstr \\"\\" + +#: example.jsx:43 +msgid \\"space invader\\" +msgstr \\"\\" + +#: example.jsx:44 +msgid \\"cat\\" +msgid_plural \\"cats\\" +msgstr[0] \\"\\" +msgstr[1] \\"\\" + +#: example.jsx:47 +msgid \\"one {foo}\\" +msgid_plural \\"many {foo}\\" +msgstr[0] \\"\\" +msgstr[1] \\"\\" + +#: example.jsx:50 +msgid \\"foo {weird} bar {hello}{test} xxx\\" +msgstr \\"\\" + +#: example.jsx:55 +msgid \\"foo: {foo}\\" +msgstr \\"\\" + +#: example.jsx:57 +msgid \\"mixed: {mixedCase}\\" +msgstr \\"\\" + +#: example.jsx:88 +#: example.jsx:131 +msgid \\"You have {count} rat.\\" +msgid_plural \\"You have {count} rats.\\" +msgstr[0] \\"\\" +msgstr[1] \\"\\" + +#: example.jsx:99 +msgid \\"Hello & World\\" +msgstr \\"\\" + +#: example.jsx:101 +msgid \\"Hey {name}, you want to {link}click me{/link} and you know it!\\" +msgstr \\"\\" + +#: example.jsx:109 +#: example.jsx:111 +msgid \\"Bye World\\" +msgstr \\"\\" + +#: example.jsx:113 +msgid \\"Some & entities: → &\\" +msgstr \\"\\" + +#: example.jsx:116 +msgid \\"Hover me\\" +msgstr \\"\\" + +#: example.jsx:119 +msgid \\" is unescaped when extracted\\" +msgstr \\"\\" + +#: example.jsx:121 +msgid \\"xxx foo bar{test}{x} y{/x} moo\\" +msgstr \\"\\" + +#: example.jsx:141 +msgid \\"Little cats:\\" +msgstr \\"\\" + +#: example.jsx:143 +msgid \\"Little dogs:\\" +msgstr \\"\\" + +#: example.jsx:145 +msgid \\"This is an emphasized dynamic value: {number}\\" +msgstr \\"\\" + +#: example.jsx:149 +msgid \\"This is an emphasized translated value: {tag}hello{/tag}\\" +msgstr \\"\\" + +#: example.jsx:155 +msgid \\"This param is using string literals: {emphasize}hello world{/emphasize}\\" +msgstr \\"\\" + +#: example.jsx:41 +#: example.jsx:141 +msgctxt \\"cat\\" +msgid \\"offspring\\" +msgstr \\"\\" + +#: example.jsx:45 +msgctxt \\"big\\" +msgid \\"cat\\" +msgid_plural \\"cats\\" +msgstr[0] \\"\\" +msgstr[1] \\"\\" + +#: example.jsx:49 +msgctxt \\"c\\" +msgid \\"one {foo}\\" +msgid_plural \\"many {foo}\\" +msgstr[0] \\"\\" +msgstr[1] \\"\\" + +#: example.jsx:56 +msgctxt \\"params-test\\" +msgid \\"foo: {foo}\\" +msgstr \\"\\" + +#: example.jsx:143 +msgctxt \\"dog\\" +msgid \\"offspring\\" +msgstr \\"\\"", +} +`; + exports[`Non-string call ignored 1`] = ` Object { "pot": "msgid \\"\\" diff --git a/tests/extract.test.js b/tests/extract.test.js index 98850ea..6e2e980 100644 --- a/tests/extract.test.js +++ b/tests/extract.test.js @@ -1,10 +1,12 @@ import extractFromFiles from '../src/tools/extract'; -const expectExtracted = (file, headers) => expect(extractFromFiles([file], headers, false)); +const expectExtracted = (file, headers, base) => + expect(extractFromFiles([file], base || process.cwd(), headers, false)); test('Messages are properly extracted', () => { expectExtracted('test-data/example.jsx', {Custom: 'Headers'}).toMatchSnapshot(); expectExtracted('test-data/example.jsx').toMatchSnapshot(); + expectExtracted('test-data/example.jsx', undefined, 'test-data').toMatchSnapshot(); }); test('Invalid stuff fails', () => {