Repository: lingui/js-lingui
Pull request #534 (2019-07-01 15:41:48)

fix(cli): noMessages is not a function
This PR for `next` branch (v3.0.0).

Something strange happens with my setup:

```txt
$ yarn compile                 
yarn run v1.15.2
$ lingui compile
/Users/nodkz/www/_npm/lingui-express-middleware/examples/simple/node_modules/@lingui/cli/lingui-compile.js:30
  if (noMessages(catalogs)) {
      ^

TypeError: noMessages is not a function
    at command (/Users/nodkz/www/_npm/lingui-express-middleware/examples/simple/node_modules/@lingui/cli/lingui-compile.js:30:7)
    at Object.<anonymous> (/Users/nodkz/www/_npm/lingui-express-middleware/examples/simple/node_modules/@lingui/cli/lingui-compile.js:115:17)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:282:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
error Command failed with exit code 1.
```

Moving `noMessage` declaration before `command` function resolves the problem.

now[bot]: 
This pull request is [automatically deployed](https://zeit.co/docs/v2/integrations/now-for-github?utm_source=automated&utm_medium=github&utm_campaign=now_bot) with [Now](https://zeit.co/now?utm_source=automated&utm_medium=github&utm_campaign=now_bot).
To access deployments, click Details below or on the icon next to each push.
	
codecov[bot]: # [Codecov](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=h1) Report
> Merging [#534](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=desc) into [next](https://codecov.io/gh/lingui/js-lingui/commit/e9fead052517ba1deda4e3491a9389d12f93437c?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/lingui/js-lingui/pull/534/graphs/tree.svg?width=650&token=CuJ1VD5BY5&height=150&src=pr)](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             next     #534   +/-   ##
=======================================
  Coverage   83.87%   83.87%           
=======================================
  Files          37       37           
  Lines        1178     1178           
  Branches      297      297           
=======================================
  Hits          988      988           
  Misses        119      119           
  Partials       71       71
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=footer). Last update [e9fead0...002ff20](https://codecov.io/gh/lingui/js-lingui/pull/534?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/examples/create-react-app/src/i18n.config.js b/examples/create-react-app/src/i18n.config.js
index a4975cd6..d8523a31 100644
--- a/examples/create-react-app/src/i18n.config.js
+++ b/examples/create-react-app/src/i18n.config.js
@@ -5,10 +5,11 @@ export const locales = {
   cs: "Česky"
 }
 
-function loadCatalog(locale) {
-  return import(`@lingui/loader!./locales/${locale}.po`).then(catalog =>
-    i18n.load(locale, catalog)
+async function loadCatalog(locale) {
+  const catalog = await import(
+    /* webpackChunkName: "i18n-[index]" */ `@lingui/loader!./locales/${locale}.po`
   )
+  i18n.load(locale, catalog)
 }
 
 export const i18n = setupI18n()
diff --git a/package.json b/package.json
index 52fc1337..66a3afb0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "private": true,
-  "version": "3.0.0-5",
+  "version": "3.0.0-6",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
diff --git a/packages/cli/src/api/compile.ts b/packages/cli/src/api/compile.ts
index efb73ef7..81fbb3ac 100644
--- a/packages/cli/src/api/compile.ts
+++ b/packages/cli/src/api/compile.ts
@@ -11,8 +11,6 @@ import pseudoLocalize from "./pseudoLocalize"
 const isString = s => typeof s === "string"
 
 export function compile(message: string) {
-  const arg = t.identifier("a")
-
   let tokens
   try {
     tokens = parse(message)
@@ -21,18 +19,14 @@ export function compile(message: string) {
       `Can't parse message. Please check correct syntax: "${message}"`
     )
   }
-  const ast = processTokens(tokens, arg)
+  const ast = processTokens(tokens)
 
   if (isString(ast)) return t.stringLiteral(ast)
 
-  return t.functionExpression(
-    null,
-    [arg],
-    t.blockStatement([t.returnStatement(ast)])
-  )
+  return ast
 }
 
-function processTokens(tokens, arg) {
+function processTokens(tokens) {
   if (!tokens.filter(token => !isString(token)).length) {
     return tokens.join("")
   }
@@ -48,7 +42,7 @@ function processTokens(tokens, arg) {
 
         // simple argument
       } else if (token.type === "argument") {
-        return t.callExpression(arg, [t.stringLiteral(token.arg)])
+        return t.arrayExpression([t.stringLiteral(token.arg)])
 
         // argument with custom format (date, number)
       } else if (token.type === "function") {
@@ -58,7 +52,7 @@ function processTokens(tokens, arg) {
         if (format) {
           params.push(t.stringLiteral(format))
         }
-        return t.callExpression(arg, params)
+        return t.arrayExpression(params)
       }
 
       // complex argument with cases
@@ -74,7 +68,7 @@ function processTokens(tokens, arg) {
       }
 
       token.cases.forEach(item => {
-        const inlineTokens = processTokens(item.tokens, arg)
+        const inlineTokens = processTokens(item.tokens)
         formatProps.push(
           t.objectProperty(
             t.identifier(item.key),
@@ -91,7 +85,7 @@ function processTokens(tokens, arg) {
         t.objectExpression(formatProps)
       ]
 
-      return t.callExpression(arg, params)
+      return t.arrayExpression(params)
     })
   )
 }
diff --git a/packages/cli/src/lingui-compile.ts b/packages/cli/src/lingui-compile.ts
index 2381f494..c9896f09 100644
--- a/packages/cli/src/lingui-compile.ts
+++ b/packages/cli/src/lingui-compile.ts
@@ -10,6 +10,12 @@ import { getCatalogs } from "./api/catalog"
 import { createCompiledCatalog } from "./api/compile"
 import { helpRun } from "./api/help"
 
+const noMessages: (catalogs: Object[]) => boolean = R.pipe(
+  R.map(R.isEmpty),
+  R.values,
+  R.all(R.equals<any>(true))
+)
+
 function command(config, options) {
   const catalogs = getCatalogs(config)
 
@@ -144,9 +150,3 @@ if (require.main === module) {
 
   console.log("Done!")
 }
-
-const noMessages: (catalogs: Object[]) => boolean = R.pipe(
-  R.map(R.isEmpty),
-  R.values,
-  R.all(R.equals<any>(true))
-)
diff --git a/packages/core/formats.js b/packages/core/formats.js
new file mode 100644
index 00000000..9d0e6854
--- /dev/null
+++ b/packages/core/formats.js
@@ -0,0 +1,1 @@
+export * from "./src/formats"
diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts
index 876c6148..f22513d6 100644
--- a/packages/core/src/context.ts
+++ b/packages/core/src/context.ts
@@ -69,22 +69,45 @@ function context({ locale, locales, values, formats, localeData }) {
 }
 
 export function interpolate(
-  translation: Function,
+  translation: string | Array<string | [string, string?, Object?]>,
   locale: string,
   locales: Locales,
   localeData: Object
 ) {
   return (values: Object, formats: Object = {}) => {
-    const message = translation(
-      context({
-        locale,
-        locales,
-        localeData,
-        formats,
-        values
-      })
-    )
-
-    return Array.isArray(message) ? message.join("").trim() : message
+    const ctx = context({
+      locale,
+      locales,
+      localeData,
+      formats,
+      values
+    })
+
+    const formatMessage = message => {
+      if (!Array.isArray(message)) return message
+
+      return message.reduce((message, token) => {
+        if (isString(token)) return message + token
+
+        const [name, type, format] = token
+
+        let interpolatedFormat = {}
+        if (format != null && !isString(format)) {
+          Object.keys(format).forEach(key => {
+            interpolatedFormat[key] = formatMessage(format[key])
+          })
+        } else {
+          interpolatedFormat = format
+        }
+
+        const value = ctx(name, type, interpolatedFormat)
+        if (value == null) return message
+
+        return message + value
+      }, "")
+    }
+
+    const result = formatMessage(translation)
+    return result.trim()
   }
 }
diff --git a/packages/core/src/dev/compile.test.js b/packages/core/src/dev/compile.test.ts
similarity index 100%
rename from packages/core/src/dev/compile.test.js
rename to packages/core/src/dev/compile.test.ts
diff --git a/packages/core/src/dev/compile.ts b/packages/core/src/dev/compile.ts
index f746953c..46807f21 100644
--- a/packages/core/src/dev/compile.ts
+++ b/packages/core/src/dev/compile.ts
@@ -7,41 +7,46 @@ function processTokens(tokens) {
     return tokens.join("")
   }
 
-  return ctx =>
-    tokens.map(token => {
-      if (isString(token)) {
-        return token
-
-        // # in plural case
-      } else if (token.type === "octothorpe") {
-        return "#"
-
-        // simple argument
-      } else if (token.type === "argument") {
-        return ctx(token.arg)
-
-        // argument with custom format (date, number)
-      } else if (token.type === "function") {
-        return ctx(token.arg, token.key, token.params[0])
-      }
+  return tokens.map(token => {
+    if (isString(token)) {
+      return token
+
+      // # in plural case
+    } else if (token.type === "octothorpe") {
+      return "#"
 
-      const offset = token.offset ? parseInt(token.offset) : undefined
+      // simple argument
+    } else if (token.type === "argument") {
+      return [token.arg]
 
-      // complex argument with cases
-      const formatProps = {}
-      token.cases.forEach(item => {
-        formatProps[item.key] = processTokens(item.tokens)
-      })
+      // argument with custom format (date, number)
+    } else if (token.type === "function") {
+      return [token.arg, token.key, token.params[0]]
+    }
 
-      return ctx(token.arg, token.type, {
+    const offset = token.offset ? parseInt(token.offset) : undefined
+
+    // complex argument with cases
+    const formatProps = {}
+    token.cases.forEach(item => {
+      formatProps[item.key] = processTokens(item.tokens)
+    })
+
+    return [
+      token.arg,
+      token.type,
+      {
         offset,
         ...formatProps
-      })
-    })
+      }
+    ]
+  })
 }
 
 // Message -> (Params -> String)
-export default function compile(message: string): Function | string {
+export default function compile(
+  message: string
+): Array<string | [string, string?, Object?]> | string {
   try {
     return processTokens(parse(message))
   } catch (e) {
diff --git a/packages/core/src/i18n.ts b/packages/core/src/i18n.ts
index 36495174..918f6fe1 100644
--- a/packages/core/src/i18n.ts
+++ b/packages/core/src/i18n.ts
@@ -16,7 +16,11 @@ export type LocaleData = {
   plurals?: Function
 }
 
-export type Messages = { [msgId: string]: string | Function }
+export type CompiledMessage =
+  | string
+  | Array<string | [string, string?, (string | Object)?]>
+
+export type Messages = { [msgId: string]: CompiledMessage }
 
 export interface MessageDescriptor {
   id?: string
@@ -167,7 +171,8 @@ export class I18n extends EventEmitter<Events> {
         : translation
     }
 
-    if (!isFunction(translation)) return translation
+    if (isString(translation)) return translation
+
     return interpolate(translation, this.locale, this.locales, this.localeData)(
       values,
       formats


Test patch:
diff --git a/packages/cli/src/api/compile.test.ts b/packages/cli/src/api/compile.test.ts
index bb829ae2..472f2194 100644
--- a/packages/cli/src/api/compile.test.ts
+++ b/packages/cli/src/api/compile.test.ts
@@ -18,36 +18,30 @@ describe("compile", function() {
   })
 
   it("should compile arguments", function() {
-    expect(getSource("{name}")).toEqual('function(a){return[a("name")]}')
+    expect(getSource("{name}")).toEqual('[["name"]]')
 
-    expect(getSource("B4 {name} A4")).toEqual(
-      'function(a){return["B4 ",a("name")," A4"]}'
-    )
+    expect(getSource("B4 {name} A4")).toEqual('["B4 ",["name"]," A4"]')
   })
 
   it("should compile arguments with formats", function() {
-    expect(getSource("{name, number}")).toEqual(
-      'function(a){return[a("name","number")]}'
-    )
+    expect(getSource("{name, number}")).toEqual('[["name","number"]]')
 
     expect(getSource("{name, number, percent}")).toEqual(
-      'function(a){return[a("name","number","percent")]}'
+      '[["name","number","percent"]]'
     )
   })
 
   it("should compile plural", function() {
     expect(getSource("{name, plural, one {Book} other {Books}}")).toEqual(
-      'function(a){return[a("name","plural",{one:"Book",other:"Books"})]}'
+      '[["name","plural",{one:"Book",other:"Books"}]]'
     )
 
     expect(
       getSource("{name, plural, one {Book} other {{name} Books}}")
-    ).toEqual(
-      'function(a){return[a("name","plural",{one:"Book",other:[a("name")," Books"]})]}'
-    )
+    ).toEqual('[["name","plural",{one:"Book",other:[["name"]," Books"]}]]')
 
     expect(getSource("{name, plural, one {Book} other {# Books}}")).toEqual(
-      'function(a){return[a("name","plural",{one:"Book",other:["#"," Books"]})]}'
+      '[["name","plural",{one:"Book",other:["#"," Books"]}]]'
     )
 
     expect(
@@ -55,21 +49,21 @@ describe("compile", function() {
         "{name, plural, offset:1 =0 {No Books} one {Book} other {# Books}}"
       )
     ).toEqual(
-      'function(a){return[a("name","plural",{offset:1,0:"No Books",one:"Book",other:["#"," Books"]})]}'
+      '[["name","plural",{offset:1,0:"No Books",one:"Book",other:["#"," Books"]}]]'
     )
   })
 
   it("should compile select", function() {
     expect(getSource("{name, select, male {He} female {She}}")).toEqual(
-      'function(a){return[a("name","select",{male:"He",female:"She"})]}'
+      '[["name","select",{male:"He",female:"She"}]]'
     )
 
     expect(getSource("{name, select, male {He} female {{name} She}}")).toEqual(
-      'function(a){return[a("name","select",{male:"He",female:[a("name")," She"]})]}'
+      '[["name","select",{male:"He",female:[["name"]," She"]}]]'
     )
 
     expect(getSource("{name, select, male {He} female {# She}}")).toEqual(
-      'function(a){return[a("name","select",{male:"He",female:"# She"})]}'
+      '[["name","select",{male:"He",female:"# She"}]]'
     )
   })