Repository: i18next/i18next-parser (js)

Issue:
React - handle `t` when used as a tagged template
## 🚀 Feature Proposal

For React, support extraction when `t` is used as a tagged template (outside of being used as a normal function).

## Motivation

`t`, when used as a tagged template, offers a more concise syntax.

## Example

```tsx
const { t } = useTranslation();
<div>{t`Language`}<div>
```

Pull request description:
Extract tagged templates
### Why am I submitting this PR

Fixes #376, more concise syntax.

### Does it fix an existing ticket?

Yes #376

### Checklist

- [Y] only relevant code is changed (make a diff before you submit the PR)
- [Y] tests are included and pass (see [details here](https://github.com/i18next/i18next-parser/blob/master/docs/development.md#tests))
- [N] documentation is changed or added
- [Y] I ran `yarn build` to compile and prettify the code

Patch:
diff --git a/dist/lexers/javascript-lexer.js b/dist/lexers/javascript-lexer.js
index 4c98bf0..919f9c9 100644
--- a/dist/lexers/javascript-lexer.js
+++ b/dist/lexers/javascript-lexer.js
@@ -57,6 +57,10 @@ JavascriptLexer = /*#__PURE__*/function (_BaseLexer) {(0, _inherits2["default"])
 
         parseCommentNode(keys, node, content);
 
+        if (node.kind === ts.SyntaxKind.TaggedTemplateExpression) {
+          entry = _this4.taggedTemplateExpressionExtractor.call(_this4, node);
+        }
+
         if (node.kind === ts.SyntaxKind.CallExpression) {
           entry = _this4.expressionExtractor.call(_this4, node);
         }
@@ -76,6 +80,30 @@ JavascriptLexer = /*#__PURE__*/function (_BaseLexer) {(0, _inherits2["default"])
       parseTree(sourceFile);
 
       return this.setNamespaces(keys);
+    } }, { key: "taggedTemplateExpressionExtractor", value:
+
+    function taggedTemplateExpressionExtractor(node) {
+      var entry = {};
+
+      var tag = node.tag,template = node.template;
+
+      var isTranslationFunction =
+      tag.text && this.functions.includes(tag.text) ||
+      tag.name && this.functions.includes(tag.name.text);
+
+      if (!isTranslationFunction) return null;
+
+      if (template.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
+        entry.key = template.text;
+      } else if (template.kind === ts.SyntaxKind.TemplateExpression) {
+        this.emit(
+        'warning',
+        'A key that is a template string must not have any interpolations.');
+
+        return null;
+      }
+
+      return entry;
     } }, { key: "expressionExtractor", value:
 
     function expressionExtractor(node) {
diff --git a/dist/lexers/jsx-lexer.js b/dist/lexers/jsx-lexer.js
index 6fd8fda..56b9973 100644
--- a/dist/lexers/jsx-lexer.js
+++ b/dist/lexers/jsx-lexer.js
@@ -30,6 +30,9 @@ JsxLexer = /*#__PURE__*/function (_JavascriptLexer) {(0, _inherits2["default"])(
           case ts.SyntaxKind.CallExpression:
             entry = _this2.expressionExtractor.call(_this2, node);
             break;
+          case ts.SyntaxKind.TaggedTemplateExpression:
+            entry = _this2.taggedTemplateExpressionExtractor(node);
+            break;
           case ts.SyntaxKind.JsxElement:
             entry = _this2.jsxExtractor.call(_this2, node, content);
             break;
diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js
index cfef1f2..480e2de 100644
--- a/src/lexers/javascript-lexer.js
+++ b/src/lexers/javascript-lexer.js
@@ -57,6 +57,10 @@ export default class JavascriptLexer extends BaseLexer {
 
       parseCommentNode(keys, node, content)
 
+      if (node.kind === ts.SyntaxKind.TaggedTemplateExpression) {
+        entry = this.taggedTemplateExpressionExtractor.call(this, node)
+      }
+
       if (node.kind === ts.SyntaxKind.CallExpression) {
         entry = this.expressionExtractor.call(this, node)
       }
@@ -78,6 +82,30 @@ export default class JavascriptLexer extends BaseLexer {
     return this.setNamespaces(keys)
   }
 
+  taggedTemplateExpressionExtractor(node) {
+    const entry = {}
+
+    const { tag, template } = node
+
+    const isTranslationFunction =
+      (tag.text && this.functions.includes(tag.text)) ||
+      (tag.name && this.functions.includes(tag.name.text))
+
+    if (!isTranslationFunction) return null
+
+    if (template.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
+      entry.key = template.text
+    } else if (template.kind === ts.SyntaxKind.TemplateExpression) {
+      this.emit(
+        'warning',
+        'A key that is a template string must not have any interpolations.'
+      )
+      return null
+    }
+
+    return entry
+  }
+
   expressionExtractor(node) {
     const entry = {}
 
diff --git a/src/lexers/jsx-lexer.js b/src/lexers/jsx-lexer.js
index 9d19ef0..e5c6933 100644
--- a/src/lexers/jsx-lexer.js
+++ b/src/lexers/jsx-lexer.js
@@ -30,6 +30,9 @@ export default class JsxLexer extends JavascriptLexer {
         case ts.SyntaxKind.CallExpression:
           entry = this.expressionExtractor.call(this, node)
           break
+        case ts.SyntaxKind.TaggedTemplateExpression:
+          entry = this.taggedTemplateExpressionExtractor.call(this, node)
+          break
         case ts.SyntaxKind.JsxElement:
           entry = this.jsxExtractor.call(this, node, content)
           break


Test patch:
diff --git a/test/lexers/javascript-lexer.test.js b/test/lexers/javascript-lexer.test.js
index 1f2ec02..057c34f 100644
--- a/test/lexers/javascript-lexer.test.js
+++ b/test/lexers/javascript-lexer.test.js
@@ -259,4 +259,34 @@ describe('JavascriptLexer', () => {
     ])
     assert.strictEqual(warningCount, 2)
   })
+
+  it('extracts non-interpolated tagged templates', () => {
+    const Lexer = new JavascriptLexer()
+    const content = 'i18n.t`some-key`'
+    assert.deepEqual(Lexer.extract(content), [
+      {
+        key: 'some-key',
+      },
+    ])
+  })
+
+  it('emits warnings on interpolated tagged templates', () => {
+    const Lexer = new JavascriptLexer()
+    const content = 'i18n.t`some-key${someVar}keykey`'
+
+    let warningCount = 0
+    Lexer.on('warning', (warning) => {
+      if (
+        warning.indexOf(
+          'A key that is a template string must not have any interpolations.'
+        ) === 0
+      ) {
+        warningCount++
+      }
+    })
+
+    Lexer.extract(content)
+
+    assert.equal(warningCount, 1)
+  })
 })