Repository: lingui/js-lingui
Pull request #588 (2019-11-14 10:56:50)

#587: Optional line numbers for lingui format
Adds configuration object `formatOptions` with key `origins`. The object is passed to the format's `write()` in the third param.

To turn off line numbers in catalogue files, configure `.linguirc` as follows:
```
"formatOptions": {
  "origins": false
}
```

now[bot]: 
This pull request is being automatically deployed with ZEIT Now ([learn more](https://zeit.co/docs/v2/integrations/now-for-github?utm_source=automated&utm_medium=github&utm_campaign=now_bot)).
To see the status of your deployment, click on the icon next to each commit.
	
now[bot]: 
Deployment failed with the following error:

~~~
Your `package.json` file is missing a `build` property inside the `script` property.
~~~
			
MarttiR: #587 
now[bot]: 
Deployment failed with the following error:

~~~
Your `package.json` file is missing a `build` property inside the `script` property.
~~~
			

Patch:
diff --git a/README.md b/README.md
index 876fc1d8..7ff417fe 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 
 <hr />
 
-[![CircleCI][Badge-CI]][CI] 
+[![CircleCI][Badge-CI]][CI]
 [![AppVeyor][Badge-AppVeyor]][AppVeyor]
 [![Code Coverage][Badge-Coverage]][Coverage]
 [![PRs Welcome][Badge-PRWelcome]][PRWelcome]
@@ -25,29 +25,29 @@ Lingui is an easy yet powerful internationalization framework for global project
 
 - **Clean and readable** - Keep your code clean and readable, while the library uses
   battle-tested and powerful **ICU MessageFormat** under the hood.
-  
+
 - **Universal** - Use it everywhere. `@lingui/core` provides the essential intl
   functionality which works in any JavaScript project while `@lingui/react` offers
   components to leverage React rendering.
 
 - **Full rich-text support** - Use React components inside localized messages
   without any limitation. Writing rich-text messages is as easy as writing JSX.
-  
+
 - **Powerful tooling** - Manage the whole intl workflow using Lingui [CLI][RefCLI]. It
   extracts messages from source code, validates messages coming from translators and
   checks that all messages are translated before shipping to production.
-  
+
 - **Unopinionated** - Integrate Lingui into your existing workflow. It supports
   message keys as well as auto generated messages. Translations are stored either in
   JSON or standard PO file, which is supported in almost all translation tools.
-  
+
 - **Lightweight and optimized** - Core library is only [1.9 kB gzipped][BundleCore],
   React components are additional [3.1 kBs gzipped][BundleReact]. That's less than Redux
   for a full-featured intl library.
-  
+
 - **Active community** - Join us on [Spectrum][Spectrum] to discuss the latest development.
   At the moment, Lingui is the most active intl project on GitHub.
-  
+
 - **Compatible with react-intl** - Low-level React API is very similar to react-intl
   and the message format is the same. It's easy to migrate an existing project.
 
@@ -98,8 +98,7 @@ For more example see the [React tutorial][TutorialReact].
 
 If you are having issues, please let us know.
 
-- Join us at [gitter.im][Gitter] to get almost instant
-  feedback.
+- Join us at [Gitter](https://gitter.im/lingui/js-lingui) to get almost instant feedback.
 - Ask question on [StackOverflow](https://stackoverflow.com/questions/ask?tags=jsLingui)
   and mark it with [Lingui](https://stackoverflow.com/questions/tagged/jslingui) tag.
 - If something doesn't work as documented, documentation is missing or if you just want
diff --git a/docs/ref/conf.rst b/docs/ref/conf.rst
index ac64e094..31032176 100644
--- a/docs/ref/conf.rst
+++ b/docs/ref/conf.rst
@@ -128,6 +128,8 @@ Each message is an object composed in the following format:
 
 Origin is filename and line number from where the message was extracted.
 
+Note that origins may produce a large amount of merge conflicts. Origins can be disabled by setting ``origins: false`` in :conf:`formatOptions`.
+
 minimal
 ^^^^^^^
 
@@ -150,6 +152,15 @@ Gettext PO file:
    msgid "MessageID"
    msgstr "Translated Message"
 
+.. config:: formatOptions
+
+formatOptions
+-------------
+
+Default: ``{ origins: true }``
+
+Object for configuring message catalog output. See individual formats for options.
+
 .. config:: sourceLocale
 
 sourceLocale
diff --git a/packages/cli/package.json b/packages/cli/package.json
index a1cadfd2..91946a01 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -58,14 +58,15 @@
     "ora": "^3.4.0",
     "pofile": "^1.0.11",
     "pseudolocale": "^1.1.0",
-    "ramda": "^0.26.1",
-    "typescript": "^2.9.2"
+    "ramda": "^0.26.1"
   },
   "devDependencies": {
-    "mockdate": "^2.0.2"
+    "mockdate": "^2.0.2",
+    "typescript": "^2.9.2"
   },
   "peerDependencies": {
     "babel-core": "6.x || ^7.0.0-bridge.0",
-    "babel-plugin-macros": "^2.4.2"
+    "babel-plugin-macros": "^2.4.2",
+    "typescript": "2 || 3"
   }
 }
diff --git a/packages/cli/src/api/catalog.js b/packages/cli/src/api/catalog.js
index d509fe72..f18d3823 100644
--- a/packages/cli/src/api/catalog.js
+++ b/packages/cli/src/api/catalog.js
@@ -30,7 +30,10 @@ export default (config: LinguiConfig): CatalogApi => {
       )
 
       const created = !fs.existsSync(filename)
-      format.write(filename, messages, { language: locale })
+      format.write(filename, messages, {
+        language: locale,
+        ...config.formatOptions
+      })
       return [created, filename]
     },
 
diff --git a/packages/cli/src/api/formats/lingui.js b/packages/cli/src/api/formats/lingui.js
index 7545281a..9128d770 100644
--- a/packages/cli/src/api/formats/lingui.js
+++ b/packages/cli/src/api/formats/lingui.js
@@ -1,13 +1,20 @@
 // @flow
 import fs from "fs"
+import * as R from "ramda"
 
 import type { TranslationsFormat } from "../types"
 
+const removeOrigins = R.map(({ origin, ...message }) => message)
+
 const format: TranslationsFormat = {
   filename: "messages.json",
 
-  write(filename, catalog) {
-    fs.writeFileSync(filename, JSON.stringify(catalog, null, 2))
+  write(filename, catalog, options = {}) {
+    let outputCatalog = catalog
+    if (options.origins === false) {
+      outputCatalog = removeOrigins(catalog)
+    }
+    fs.writeFileSync(filename, JSON.stringify(outputCatalog, null, 2))
   },
 
   read(filename) {
diff --git a/packages/cli/src/api/types.js b/packages/cli/src/api/types.js
index 5a7d3164..b91302a3 100644
--- a/packages/cli/src/api/types.js
+++ b/packages/cli/src/api/types.js
@@ -6,12 +6,17 @@ export type LinguiConfig = {|
   sourceLocale: string,
   fallbackLocale: string,
   pseudoLocale: string,
+  formatOptions: ?FormatOptions,
   srcPathDirs: Array<string>,
   srcPathIgnorePatterns: Array<string>,
   format: "lingui" | "minimal" | "po",
   sorting: Sorting
 |}
 
+export type FormatOptions = {
+  origins: ?boolean
+}
+
 export type Sorting = "messageId" | "origin"
 
 export type IdempotentResult<T> = [boolean, ?T] // [ created, result ]
diff --git a/packages/conf/src/index.js b/packages/conf/src/index.js
index 72dbc383..8ae70fa6 100644
--- a/packages/conf/src/index.js
+++ b/packages/conf/src/index.js
@@ -35,6 +35,9 @@ export const defaultConfig = {
   srcPathIgnorePatterns: [NODE_MODULES],
   sorting: "messageId",
   format: "lingui",
+  formatOptions: {
+    origins: true
+  },
   rootDir: ".",
   extractBabelOptions: {
     plugins: [],
diff --git a/packages/core/index.js b/packages/core/index.js
index 46e72b16..b1633e9f 100644
--- a/packages/core/index.js
+++ b/packages/core/index.js
@@ -1,1 +1,2 @@
+// @flow
 export * from "./src"
diff --git a/packages/react/src/I18nProvider.js b/packages/react/src/I18nProvider.js
index 4e85d224..ecec00b6 100644
--- a/packages/react/src/I18nProvider.js
+++ b/packages/react/src/I18nProvider.js
@@ -27,7 +27,7 @@ type LinguiPublisher = {|
 
   unsubscribe(callback: Function): void,
 
-  update(?{ catalogs?: Catalogs, language?: string, locales?: string }): void
+  update(?{ catalogs?: Catalogs, language?: string, locales?: Locales }): void
 |}
 
 /*
@@ -54,7 +54,11 @@ export function makeLinguiPublisher(i18n: I18n): LinguiPublisher {
     },
 
     update(
-      params: ?{ catalogs?: Catalogs, language?: string, locales?: string } = {}
+      params: ?{
+        catalogs?: Catalogs,
+        language?: string,
+        locales?: Locales
+      } = {}
     ) {
       if (!params) return
       const { catalogs, language, locales } = params
diff --git a/packages/react/src/Select.js b/packages/react/src/Select.js
index 733327eb..cb40b446 100644
--- a/packages/react/src/Select.js
+++ b/packages/react/src/Select.js
@@ -48,7 +48,6 @@ const Select = withI18n()(
 
 const PluralFactory = (ordinal = false) => {
   const displayName = !ordinal ? "Plural" : "SelectOrdinal"
-  const pluralType = !ordinal ? "plural" : "selectOrdinal"
 
   return class extends React.Component<*, PluralProps> {
     displayName = displayName
@@ -61,6 +60,7 @@ const PluralFactory = (ordinal = false) => {
 
     render() {
       const { className, render, i18n, value, offset, ...props } = this.props
+      const getPluralValue = !ordinal ? i18n.plural : i18n.selectOrdinal
 
       // i18n.selectOrdinal/plural uses numbers for exact matches (1, 2),
       // while SelectOrdinal/Plural has to use strings (_1, _2).
@@ -80,7 +80,7 @@ const PluralFactory = (ordinal = false) => {
         <Render
           className={className}
           render={render}
-          value={i18n[pluralType](pluralProps)}
+          value={getPluralValue(pluralProps)}
         />
       )
     }
diff --git a/packages/react/src/format.js b/packages/react/src/format.js
index 95f72101..1a2d9e77 100644
--- a/packages/react/src/format.js
+++ b/packages/react/src/format.js
@@ -2,7 +2,7 @@
 import { cloneElement } from "react"
 
 // match <0>paired</0> and <1/> unpaired tags
-const tagRe = /<(\d+)>(.*)<\/\1>|<(\d+)\/>/
+const tagRe = /<(\d+)>(.*?)<\/\1>|<(\d+)\/>/
 const nlRe = /(?:\r\n|\r|\n)/g
 
 /**
diff --git a/scripts/build/results.json b/scripts/build/results.json
index 630885e6..fcc0073e 100644
--- a/scripts/build/results.json
+++ b/scripts/build/results.json
@@ -2,7 +2,7 @@
   "bundleSizes": {
     "@lingui/core.development.js (NODE_DEV)": {
       "size": 14825,
-      "gzip": 3861
+      "gzip": 3860
     },
     "@lingui/core.production.min.js (NODE_PROD)": {
       "size": 4286,
@@ -18,7 +18,7 @@
     },
     "dev.development.js (NODE_DEV)": {
       "size": 14825,
-      "gzip": 3861
+      "gzip": 3860
     },
     "dev.production.min.js (NODE_PROD)": {
       "size": 4286,


Test patch:
diff --git a/packages/react/src/I18nProvider.test.js b/packages/react/src/I18nProvider.test.js
index 1099cb7c..e9114190 100644
--- a/packages/react/src/I18nProvider.test.js
+++ b/packages/react/src/I18nProvider.test.js
@@ -165,14 +165,14 @@ describe("I18nPublisher", function() {
     })
     expect(linguiPublisher.i18n.language).toEqual("fr")
     expect(linguiPublisher.i18n.messages).toEqual({ msg: "salut!" })
-    expect(linguiPublisher.i18n.languageData.plurals()).toEqual("Function")
+    expect(typeof linguiPublisher.i18n.languageData.plurals).toEqual("function")
   })
 
   it("should subscribe/unsubscribe listeners for context changes", function() {
     const linguiPublisher = makeLinguiPublisher(
       setupI18n({
         language: "en",
-        catalogs: { en: {} }
+        catalogs: { en: { messages: {} } }
       })
     )
     const listener = jest.fn()
@@ -191,7 +191,7 @@ describe("I18nPublisher", function() {
     const linguiPublisher = makeLinguiPublisher(
       setupI18n({
         language: "en",
-        catalogs: { en: {}, fr: {} }
+        catalogs: { en: { messages: {} }, fr: { messages: {} } }
       })
     )
     linguiPublisher.subscribe(listener)
diff --git a/packages/react/src/Select.test.js b/packages/react/src/Select.test.js
index 1f90045d..dbae8c87 100644
--- a/packages/react/src/Select.test.js
+++ b/packages/react/src/Select.test.js
@@ -9,7 +9,7 @@ describe("Categories", function() {
     setupI18n({
       language: language,
       locales: locales,
-      catalogs: { [language]: {} }
+      catalogs: { [language]: { messages: {} } }
     })
   const languageContext = (language, locales) => ({
     context: { linguiPublisher: { i18n: i18n(language, locales) } }
diff --git a/packages/react/src/format.test.js b/packages/react/src/format.test.js
index 5d81a483..6b1d75f8 100644
--- a/packages/react/src/format.test.js
+++ b/packages/react/src/format.test.js
@@ -53,4 +53,16 @@ describe("formatElements", function() {
       )
     )
   })
+
+  it("should format multiple usages of same element in the same scope", function() {
+    expect(
+      html(
+        formatElements("<0>First</0> second <0>third</0> <0>fourth</0>", [
+          <sup />
+        ])
+      )
+    ).toEqual(
+      wrap("<sup>First</sup> second <sup>third</sup> <sup>fourth</sup>")
+    )
+  })
 })