Repository: i18next/i18next-parser
Pull request #701 (2022-11-21 14:33:10)

feat: parse namespace from `t` type arguments
### Why am I submitting this PR

It allows us to use type argument of `t` function (see [type definition](https://github.com/i18next/i18next/blob/master/index.d.ts#L881)) to set default namespace when parsing content.

This is particularly useful when you are passing `t` from component, for example

```
// component.tsx
export const UserDetail = () => {
  const { t } = useTranslation("user");
  const templateData = useMemo(() => getData(t), [t]);
  return <UserProfile help={t("help.tooltip")} content={templateData} />
};

// util.ts
export const getData = (t: TFunction<"user">) => ({
  greeting: t("user.greeting", "Welcome!"),
  intro: t("user.body"),
})
```

Even though the example above might not be the best use case, it should be good enough to explain how we can benefit from this.

In the case above, you'd normally end up with one translation in `user` namespace and the rest in the default namespace.
With the proposed changes, we would parse the function declarations to extract namespace from type arguments of `t` so that all parsed keys end up in `user` namespace.

What do you guys think about this? Would it be somewhat helpful? Have you found yourselves dealing with this kind of cases in the past?

### Does it fix an existing ticket?

Yes #703

### Checklist

- [x] only relevant code is changed (make a diff before you submit the PR)
- [x] tests are included and pass: `yarn test` (see [details here](https://github.com/i18next/i18next-parser/blob/master/docs/development.md#tests))
- [ ] documentation is changed or added



Patch:
diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js
index 7772cfe..1c7aee3 100644
--- a/src/lexers/javascript-lexer.js
+++ b/src/lexers/javascript-lexer.js
@@ -74,6 +74,13 @@ export default class JavascriptLexer extends BaseLexer {
 
       parseCommentNode(keys, node, content)
 
+      if (
+        node.kind === ts.SyntaxKind.ArrowFunction ||
+        node.kind === ts.SyntaxKind.FunctionDeclaration
+      ) {
+        this.functionParamExtractor.call(this, node)
+      }
+
       if (node.kind === ts.SyntaxKind.TaggedTemplateExpression) {
         entry = this.taggedTemplateExpressionExtractor.call(this, node)
       }
@@ -99,6 +106,32 @@ export default class JavascriptLexer extends BaseLexer {
     return this.setNamespaces(keys)
   }
 
+  /** @param {ts.FunctionLikeDeclaration} node */
+  functionParamExtractor(node) {
+    const tFunctionParam =
+      node.parameters &&
+      node.parameters.find(
+        (param) =>
+          param.name &&
+          param.name.kind === ts.SyntaxKind.Identifier &&
+          this.functions.includes(param.name.text)
+      )
+
+    if (
+      tFunctionParam &&
+      tFunctionParam.type &&
+      tFunctionParam.type.typeName.text === 'TFunction'
+    ) {
+      const { typeArguments } = tFunctionParam.type
+      if (
+        typeArguments.length &&
+        typeArguments[0].kind === ts.SyntaxKind.LiteralType
+      ) {
+        this.defaultNamespace = typeArguments[0].literal.text
+      }
+    }
+  }
+
   taggedTemplateExpressionExtractor(node) {
     const entry = {}
 


Test patch:
diff --git a/test/lexers/javascript-lexer.test.js b/test/lexers/javascript-lexer.test.js
index 8df2776..491ed83 100644
--- a/test/lexers/javascript-lexer.test.js
+++ b/test/lexers/javascript-lexer.test.js
@@ -128,6 +128,17 @@ describe('JavascriptLexer', () => {
     done()
   })
 
+  it('parses namespace from `t` type argument', (done) => {
+    const Lexer = new JavascriptLexer()
+    const content = `
+      const content = (t: TFunction<"foo">) => ({
+        title: t("bar"),
+      })
+    `
+    assert.deepEqual(Lexer.extract(content), [{ key: 'bar', namespace: 'foo' }])
+    done()
+  })
+
   it("does not parse text with `doesn't` or isolated `t` in it", (done) => {
     const Lexer = new JavascriptLexer()
     const js =