Repository: lingui/js-lingui
Pull request #1152 (2021-10-20 23:31:53)

feat: add the ability to extract concatenated comments
Hello! Once again thanks for lingui. We're using it more and more at Smartsheet.

I ran into this after my translation comments were quite verbose. I wanted to break them into multiple lines and concatenate them:

...
comment: "first long line" + 
"second long line",
...

1 question, if you'd like to go forward with this PR: should this concatenation extraction be applied to the id as well?

tannerlyons: Howdy, I don't understand the following error in the pipeline as it relates to my changes: https://github.com/lingui/js-lingui/runs/4014369422?check_suite_focus=true#step:3:40

Any advice on how I can go about solving this?

```
...
$ node ./scripts/build/
...
- @lingui/react.development.js (node_dev)
✖ @lingui/react.development.js (node_dev)
-- PLUGIN_ERROR (rpt2) --
packages/react/src/I18nProvider.tsx:2:22 - error TS6059: File '/home/runner/work/js-lingui/js-lingui/packages/core/src/index.ts' is not under 'rootDir' '/home/runner/work/js-lingui/js-lingui/packages/react/src'. 'rootDir' is expected to contain all source files.

2 import { I18n } from "@lingui/core"
                       ~~~~~~~~~~~~~~

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
semoal: Is not your problem Tanner don't worry, the pr looks good to me mate. Will fix the CI asap

Patch:
diff --git a/packages/babel-plugin-extract-messages/src/index.ts b/packages/babel-plugin-extract-messages/src/index.ts
index 4ebfb2fb..91d9f6a1 100644
--- a/packages/babel-plugin-extract-messages/src/index.ts
+++ b/packages/babel-plugin-extract-messages/src/index.ts
@@ -96,6 +96,19 @@ function mapReplacer(key, value) {
   return value;
 }
 
+function extractStringContatentation(t, node, error): string {
+  if (t.isStringLiteral(node)) {
+    return node.value
+  } else if (t.isBinaryExpression(node)) {
+    return (
+      extractStringContatentation(t, node.left, error) +
+      extractStringContatentation(t, node.right, error)
+    )
+  } else {
+    throw error
+  }
+}
+
 export default function ({ types: t }) {
   let localTransComponentName
 
@@ -282,13 +295,31 @@ export default function ({ types: t }) {
         path.node.properties
           .filter(({ key }) => copyProps.indexOf(key.name) !== -1)
           .forEach(({ key, value }, i) => {
+            // By default, the value is just the string value of the object property.
+            let valueToExtract = value.value;
+
             if (key.name === "comment" && !t.isStringLiteral(value)) {
-              throw path
+              // Comments can be single or multi-line strings.
+              const errorIfNotAString = path
                 .get(`properties.${i}.value`)
                 .buildCodeFrameError("Only strings are supported as comments.")
+
+              if (t.isBinaryExpression(value)) {
+                valueToExtract = extractStringContatentation(
+                  t,
+                  value,
+                  errorIfNotAString
+                )
+              } else {
+                throw errorIfNotAString
+              }
+            } else if (key.name === "id") {
+                const isIdLiteral = !value.value && t.isTemplateLiteral(value)
+                if (isIdLiteral) {
+                    valueToExtract = value?.quasis[0]?.value?.cooked;
+                }
             }
-            const isIdLiteral = !value.value && key.name === "id" && t.isTemplateLiteral(value)
-            props[key.name] = isIdLiteral ? value?.quasis[0]?.value?.cooked : value.value
+            props[key.name] = valueToExtract;
           })
 
         collectMessage(path, file, props)
diff --git a/packages/cli/src/services/translationIO.ts b/packages/cli/src/services/translationIO.ts
index 07c391c3..3fca19c2 100644
--- a/packages/cli/src/services/translationIO.ts
+++ b/packages/cli/src/services/translationIO.ts
@@ -43,7 +43,8 @@ export default function syncProcess(config, options) {
 // Cf. https://translation.io/docs/create-library#initialization
 function init(config, options, successCallback, failCallback) {
   const sourceLocale  = config.sourceLocale || 'en'
-  const targetLocales = config.locales.filter((value) => value != sourceLocale)
+  const pseudoLocale  = config.pseudoLocale || 'pseudo'
+  const targetLocales = config.locales.filter((value) => value != sourceLocale && value != pseudoLocale)
   const paths         = poPathsPerLocale(config)
 
   let segments = {}


Test patch:
diff --git a/packages/babel-plugin-extract-messages/test/__snapshots__/index.ts.snap b/packages/babel-plugin-extract-messages/test/__snapshots__/index.ts.snap
index b77949ae..bbb46d84 100644
--- a/packages/babel-plugin-extract-messages/test/__snapshots__/index.ts.snap
+++ b/packages/babel-plugin-extract-messages/test/__snapshots__/index.ts.snap
@@ -131,7 +131,7 @@ Object {
       origin: Array [
         Array [
           js-without-macros.js,
-          11,
+          13,
         ],
       ],
     },
@@ -153,7 +153,7 @@ Object {
     origin: Array [
       Array [
         js-without-macros.js,
-        7,
+        9,
       ],
     ],
   },
@@ -166,12 +166,23 @@ Object {
       ],
     ],
   },
+  Multiline: Object {
+    extractedComments: Array [
+      this is 2 lines long,
+    ],
+    origin: Array [
+      Array [
+        js-without-macros.js,
+        7,
+      ],
+    ],
+  },
   Values {param}: Object {
     extractedComments: Array [],
     origin: Array [
       Array [
         js-without-macros.js,
-        9,
+        11,
       ],
     ],
   },
diff --git a/packages/babel-plugin-extract-messages/test/fixtures/js-without-macros.js b/packages/babel-plugin-extract-messages/test/fixtures/js-without-macros.js
index 832361b9..c98941ba 100644
--- a/packages/babel-plugin-extract-messages/test/fixtures/js-without-macros.js
+++ b/packages/babel-plugin-extract-messages/test/fixtures/js-without-macros.js
@@ -4,6 +4,8 @@ const msg = /*i18n*/{id: 'Message'}
 
 const withDescription = /*i18n*/{id: 'Description', comment: "description"}
 
+const withMultilineComment = /*i18n*/{id: 'Multiline', comment: "this is " + "2 lines long"}
+
 const withId = /*i18n*/{id: 'ID', message: 'Message with id'}
 
 const withValues = /*i18n*/{id: 'Values {param}', values: { param: param }}