Repository: lingui/js-lingui (ts)

Issue:
Support id and comment in `t` macro
This is often requested feature — `t` macro should accept custom `id` and/or `comment`. Using `defineMessage` in combination with `i18n._` is unnecessary verbose.

## Proposal

TLDR: Same api as `defineMessage`, but the output will be wrapped in `i18n._` as `t` and other macros does:

```
t({
  id: 'msgId,
  message: 'text'
})
// ↓ ↓ ↓ ↓ ↓ ↓
i18n._(/*i18n*/{ id: "msgId", values: { name }, message: 'text' })


t({
  id: 'msgId,
  message: 'text',
  comment: 'description for translators'
})
// ↓ ↓ ↓ ↓ ↓ ↓
i18n._(/*i18n*/{ id: "msgId", values: { name }, message: 'text', comment: 'description for translators' })


t({
  id: 'msgId,
  message: plural(value, { one: '...', other: '...' })
})

// ↓ ↓ ↓ ↓ ↓ ↓
i18n._(/*i18n*/{ id: "msgId", values: { name }, message: '{value, plural, one {...} other {...}' })

// Important! This should still work:
t`Hello World`
i18n._(/*i18n*/{ id: 'Hello World')
```

This should be fairly easy, because all logic for parsing messge descriptors is already inside `defineMessage` macro. It just needs an extra `if` in the right location, so the `t` macro can be used either as a template literal tag or a function.

## Tasks

- [x] update `t` macro in [`macroJs.ts`](https://github.com/lingui/js-lingui/blob/main/packages/macro/src/macroJs.ts#L213) to also accept message descriptor, the same way how `defineMessage` does
- [x] add tests to [`js-t.ts`](https://github.com/lingui/js-lingui/blob/main/packages/macro/test/js-t.ts) to cover new usecases
- [x] update `t` macro docs

Related issues: #797 #701

Pull request:
feat: accept t as function
Closes #800

Couldn't update the docs because i'm shit writing tables with RST.. any suggerence? haha

Patch:
diff --git a/docs/misc/showroom.rst b/docs/misc/showroom.rst
index c2e48155..1ce6402d 100644
--- a/docs/misc/showroom.rst
+++ b/docs/misc/showroom.rst
@@ -15,4 +15,3 @@ Feel free to `send a PR <https://github.com/lingui/js-lingui/issues/new>`__ to l
 - `Upcount <https://github.com/madisvain/upcount>`__
 - `Symbolovník <http://www.symbolovnik.cz>`__
 - `Notos <https://www.notos.co>`__
-- `Greenlight <https://greenlightready.com>`__
diff --git a/docs/ref/macro.rst b/docs/ref/macro.rst
index 3d7f4b3e..de05cbcd 100644
--- a/docs/ref/macro.rst
+++ b/docs/ref/macro.rst
@@ -138,6 +138,13 @@ Examples of JS macros
 +-------------------------------------------------------------+--------------------------------------------------------------------+
 | .. code-block:: js                                          | .. code-block:: js                                                 |
 |                                                             |                                                                    |
+|    t({                                                      |    i18n._(/*i18n*/{                                                |
+|       id: "msg.refresh",                                    |      id: "msg.refresh",                                            |
+|       message: "Refresh inbox"                              |      message: "Refresh inbox"                                      |
+|    })                                                       |    })                                                              |
++-------------------------------------------------------------+--------------------------------------------------------------------+
+| .. code-block:: js                                          | .. code-block:: js                                                 |
+|                                                             |                                                                    |
 |    describeMessage({                                        |    /*i18n*/{                                                       |
 |       id: "msg.refresh",                                    |      id: "msg.refresh",                                            |
 |       message: "Refresh inbox"                              |      message: "Refresh inbox"                                      |
@@ -174,7 +181,7 @@ Examples of JSX macros
 +-------------------------------------------------------------+--------------------------------------------------------------------+
 | .. code-block:: jsx                                         | .. code-block:: jsx                                                |
 |                                                             |                                                                    |
-|    <Trans id=" msg.refresh">                                |    <Trans                                                          |
+|    <Trans id="msg.refresh">                                 |    <Trans                                                          |
 |       Refresh inbox                                         |       id="msg.refresh"                                             |
 |    </Trans>                                                 |       message="Refresh inbox"                                      |
 |                                                             |    />                                                              |
@@ -271,6 +278,46 @@ other expressions are referenced by numeric index:
      values: { 0: date(name) }
    })
 
+It's also possible to pass custom ``id`` and ``comment`` for translators by
+calling ``t`` macro with a message descriptor:
+
+.. code-block:: jsx
+
+   import { t } from "@lingui/macro"
+   const message = t({
+      id: 'msg.hello',
+      comment: 'Greetings at the homepage',
+      message: `Hello ${name}`
+   })
+
+   // ↓ ↓ ↓ ↓ ↓ ↓
+
+   import { i18n } from "@lingui/core"
+   const message = i18n._(/*i18n*/{
+      id: 'msg.hello',
+      comment: 'Greetings at the homepage',
+      message: 'Hello {name}'
+   })
+
+In this case the ``message`` is used as a default message and it's transformed
+as if it were wrapped in ``t`` macro. ``message`` also accepts any other macros:
+
+.. code-block:: jsx
+
+   import { t } from "@lingui/macro"
+   const message = t({
+      id: 'msg.plural',
+      message: plural(value, { one: "...", other: "..." })
+   })
+
+   // ↓ ↓ ↓ ↓ ↓ ↓
+
+   import { i18n } from "@lingui/core"
+   const message = i18n._(/*i18n*/{
+      id: 'msg.plural',
+      message: '{value, plural, one {...} other {...}}'
+   })
+
 plural
 ^^^^^^
 
@@ -363,6 +410,11 @@ two counters:
    However, simple is better because in the end it's the translator who's gonna
    have to translate these long and complex strings.
 
+.. important::
+
+   Use ``plural`` inside :jsmacro:`t` macro if you want to add custom ``id``
+   or ``comment`` for translators.
+
 selectOrdinal
 ^^^^^^^^^^^^^
 
@@ -393,6 +445,11 @@ cardinal plural forms it uses ordinal forms:
      values: { count }
    })
 
+.. important::
+
+   Use ``selectOrdinal`` inside :jsmacro:`t` macro if you want to add custom ``id``
+   or ``comment`` for translators.
+
 select
 ^^^^^^
 
@@ -422,6 +479,11 @@ provided in ``options`` object which key matches exactly ``value``:
      values: { gender }
    })
 
+.. important::
+
+   Use ``select`` inside :jsmacro:`t` macro if you want to add custom ``id``
+   or ``comment`` for translators.
+
 defineMessage
 ^^^^^^^^^^^^^
 
diff --git a/packages/macro/src/macroJs.ts b/packages/macro/src/macroJs.ts
index bbf6b22a..1419d00b 100644
--- a/packages/macro/src/macroJs.ts
+++ b/packages/macro/src/macroJs.ts
@@ -98,6 +98,11 @@ export default class MacroJs {
       return
     }
 
+    if (this.types.isCallExpression(path.node) && this.isIdentifier(path.node.callee, "t")) {
+      this.replaceTAsFunction(path)
+      return
+    }
+
     const tokens = this.tokenizeNode(path.node)
 
     const messageFormat = new ICUMessageFormat()
@@ -142,6 +147,26 @@ export default class MacroJs {
     path.replaceWith(descriptor)
   }
 
+  /**
+   * macro `t` is called with MessageDescriptor, after that
+   * we create a new node to append it to i18n._
+   */
+  replaceTAsFunction = (path) => {
+    const descriptor = this.processDescriptor(path.node.arguments[0])
+    const newNode = this.types.callExpression(
+      this.types.memberExpression(
+        this.types.identifier(this.i18nImportName),
+        this.types.identifier("_")
+      ),
+      [descriptor]
+    )
+
+    this.addExtractMark(path)
+
+    // @ts-ignore
+    path.replaceWith(newNode)
+  }
+
   /**
    * `processDescriptor` expand macros inside messsage descriptor.
    * Message descriptor is used in `defineMessage`.


Test patch:
diff --git a/packages/macro/test/js-t.ts b/packages/macro/test/js-t.ts
index 43c8c40d..abbfe02f 100644
--- a/packages/macro/test/js-t.ts
+++ b/packages/macro/test/js-t.ts
@@ -82,6 +82,28 @@ export default [
         i18n._("Multiline\\nstring")
       `,
   },
+  {
+    name: "Support id and comment in t macro as callExpression",
+    input: `
+        import { t } from '@lingui/macro'
+        t({
+          id: 'msgId_2',
+          message: 'text',
+          comment: 'description for translators'
+        })
+        t({ id: 'msgId', comment: 'description for translators', message: plural(val, { one: '...', other: '...' }) })
+      `,
+    expected: `
+      import { i18n } from "@lingui/core"
+      /*i18n*/
+      i18n._({ id: "msgId_2", message: 'text', comment: 'description for translators' })
+      
+      /*i18n*/
+      i18n._({ id: "msgId", comment: 'description for translators', message: '{val, plural, one {...} other {...}}', values: {
+        val: val,
+      } })
+      `,
+  },
   {
     name: "Newlines after continuation character are removed",
     filename: "js-t-continuation-character.js",