diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js index 65cf44bd..22555527 100644 --- a/src/lexers/javascript-lexer.js +++ b/src/lexers/javascript-lexer.js @@ -208,9 +208,16 @@ export default class JavascriptLexer extends BaseLexer { } const isTranslationFunction = + // If the expression is a string literal, we can just check if it's in the + // list of functions (node.expression.text && this.functions.includes(node.expression.text)) || + // Support the case where the function is contained in a namespace, i.e. + // match `i18n.t()` when this.functions = ['t']. (node.expression.name && - this.functions.includes(node.expression.name.text)) + this.functions.includes(node.expression.name.text)) || + // Support matching the namespace as well, i.e. match `i18n.t()` but _not_ + // `l10n.t()` when this.functions = ['i18n.t'] + this.functions.includes(this.expressionToName(node.expression)) if (isTranslationFunction) { const keyArgument = node.arguments.shift() @@ -376,4 +383,24 @@ export default class JavascriptLexer extends BaseLexer { return string } + + /** + * Recursively computes the name of a dot-separated expression, e.g. `t` or `t.ns` + * @type {(expression: ts.LeftHandSideExpression | ts.JsxTagNameExpression) => string} + */ + expressionToName(expression) { + if (expression) { + if (expression.text) { + return expression.text + } else if (expression.name) { + return [ + this.expressionToName(expression.expression), + this.expressionToName(expression.name), + ] + .filter((s) => s && s.length > 0) + .join('.') + } + } + return undefined + } } diff --git a/src/lexers/jsx-lexer.js b/src/lexers/jsx-lexer.js index 9b5dd5c4..2f7d6215 100644 --- a/src/lexers/jsx-lexer.js +++ b/src/lexers/jsx-lexer.js @@ -91,7 +91,9 @@ export default class JsxLexer extends JavascriptLexer { const getKey = (node) => getPropValue(node, this.attr) - if (this.componentFunctions.includes(tagNode.tagName.text)) { + if ( + this.componentFunctions.includes(this.expressionToName(tagNode.tagName)) + ) { const entry = {} entry.key = getKey(tagNode) diff --git a/test/lexers/javascript-lexer.test.js b/test/lexers/javascript-lexer.test.js index 98b16cd5..5fe9bddb 100644 --- a/test/lexers/javascript-lexer.test.js +++ b/test/lexers/javascript-lexer.test.js @@ -164,11 +164,13 @@ describe('JavascriptLexer', () => { }) it('supports a `functions` option', (done) => { - const Lexer = new JavascriptLexer({ functions: ['tt', '_e'] }) - const content = 'tt("first") + _e("second")' + const Lexer = new JavascriptLexer({ functions: ['tt', '_e', 'f.g'] }) + const content = 'tt("first") + _e("second") + x.tt("third") + f.g("fourth")' assert.deepEqual(Lexer.extract(content), [ { key: 'first' }, { key: 'second' }, + { key: 'third' }, + { key: 'fourth' }, ]) done() }) diff --git a/test/lexers/jsx-lexer.test.js b/test/lexers/jsx-lexer.test.js index e4eaa8d0..9e52e3e7 100644 --- a/test/lexers/jsx-lexer.test.js +++ b/test/lexers/jsx-lexer.test.js @@ -180,17 +180,27 @@ describe('JsxLexer', () => { it('extracts keys from user-defined components', (done) => { const Lexer = new JsxLexer({ - componentFunctions: ['Translate', 'FooBar'], + componentFunctions: [ + 'Translate', + 'FooBar', + 'Namespace.A', + 'Double.Namespace.B', + ], }) const content = `
Something to translate. asdf + asdf Lorum Ipsum + Namespaced + Namespaced2
` assert.deepEqual(Lexer.extract(content), [ { key: 'something', defaultValue: 'Something to translate.' }, { key: 'asdf', defaultValue: 'Lorum Ipsum' }, + { key: 'namespaced', defaultValue: 'Namespaced' }, + { key: 'namespaced2', defaultValue: 'Namespaced2' }, ]) done() })