diff --git a/packages/cli/package.json b/packages/cli/package.json index 8fa841965..65019fbfa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -53,6 +53,7 @@ "@babel/core": "^7.20.12", "@lingui/babel-plugin-extract-messages": "3.17.1", "@lingui/conf": "3.17.1", + "@lingui/message-id": "3.17.1", "@lingui/core": "3.17.1", "@messageformat/parser": "^5.0.0", "babel-plugin-macros": "^3.0.1", diff --git a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap index 8a199800b..903c09490 100644 --- a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap +++ b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap @@ -17,6 +17,7 @@ Object { exports[`Catalog collect should extract messages from source files 1`] = ` Object { Component A: Object { + context: undefined, extractedComments: Array [], message: undefined, origin: Array [ @@ -27,6 +28,7 @@ Object { ], }, Component B: Object { + context: undefined, extractedComments: Array [], message: undefined, origin: Array [ @@ -37,6 +39,7 @@ Object { ], }, Hello World: Object { + context: undefined, extractedComments: Array [ Comment A, Comment A again, @@ -59,6 +62,7 @@ Object { ], }, custom.id: Object { + context: undefined, extractedComments: Array [], message: Message with id, origin: Array [ @@ -74,6 +78,7 @@ Object { exports[`Catalog collect should extract only files passed on options 1`] = ` Object { Component A: Object { + context: undefined, extractedComments: Array [], message: undefined, origin: Array [ @@ -84,6 +89,7 @@ Object { ], }, Hello World: Object { + context: undefined, extractedComments: Array [ Comment A, Comment A again, @@ -106,6 +112,7 @@ Object { ], }, custom.id: Object { + context: undefined, extractedComments: Array [], message: Message with id, origin: Array [ @@ -122,75 +129,82 @@ exports[`Catalog collect should support Flow syntax if enabled 1`] = `Object {}` exports[`Catalog collect should support JSX and Typescript 1`] = ` Object { - Description: Object { - extractedComments: Array [ - description, - ], - message: undefined, + ID Some: Object { + context: undefined, + extractedComments: Array [], + message: Message with id some, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 6, + 11, ], ], }, - Hi, my name is {name}: Object { + MHrjPM: Object { + context: undefined, extractedComments: Array [], - message: undefined, + message: Title, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 17, + 19, ], ], }, - ID Some: Object { - extractedComments: Array [], - message: Message with id some, + Nu4oKW: Object { + context: undefined, + extractedComments: Array [ + description, + ], + message: Description, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 11, + 6, ], ], }, - Message: Object { + YikuIL: Object { + context: Context1, extractedComments: Array [], - message: undefined, + message: Some message, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 4, + 18, ], ], }, - Some message: Object { + esnaQO: Object { + context: undefined, extractedComments: Array [], - message: undefined, + message: {count, plural, one {# book} other {# books}}, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 18, + 21, ], ], }, - Title: Object { + stjtW+: Object { + context: undefined, extractedComments: Array [], - message: undefined, + message: Hi, my name is {name}, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 19, + 17, ], ], }, - {count, plural, one {# book} other {# books}}: Object { + xDAtGP: Object { + context: undefined, extractedComments: Array [], - message: undefined, + message: Message, origin: Array [ Array [ collect-typescript-jsx/macro.tsx, - 21, + 4, ], ], }, diff --git a/packages/cli/src/api/formats/__snapshots__/po.test.ts.snap b/packages/cli/src/api/formats/__snapshots__/po.test.ts.snap index 5f772447c..1f916f86a 100644 --- a/packages/cli/src/api/formats/__snapshots__/po.test.ts.snap +++ b/packages/cli/src/api/formats/__snapshots__/po.test.ts.snap @@ -162,6 +162,15 @@ msgstr "Static message" msgid "withOrigin" msgstr "Message with origin" +msgctxt "my context" +msgid "withContext" +msgstr "Message with context" + +#, generated-id +msgctxt "my context" +msgid "with generated id" +msgstr "" + #: src/App.js:4 #: src/Component.js:2 msgid "withMultipleOrigins" diff --git a/packages/cli/src/api/formats/po.test.ts b/packages/cli/src/api/formats/po.test.ts index 5f7958d2f..68a5a320f 100644 --- a/packages/cli/src/api/formats/po.test.ts +++ b/packages/cli/src/api/formats/po.test.ts @@ -32,6 +32,15 @@ describe("pofile format", () => { translation: "Message with origin", origin: [["src/App.js", 4]], }, + withContext: { + translation: "Message with context", + context: "my context", + }, + Dgzql1: { + message: "with generated id", + translation: "", + context: "my context", + }, withMultipleOrigins: { translation: "Message with multiple origin", origin: [ @@ -97,6 +106,30 @@ describe("pofile format", () => { expect(actual).toMatchSnapshot() }) + it("should serialize and deserialize messages with generated id", () => { + mockFs({ + locale: { + en: mockFs.directory(), + }, + }) + + const catalog: CatalogType = { + // with generated id + Dgzql1: { + message: "with generated id", + translation: "", + context: "my context", + }, + } + + const filename = path.join("locale", "en", "messages.po") + format.write(filename, catalog, { origins: true, locale: "en" }) + + const actual = format.read(filename) + mockFs.restore() + expect(actual).toMatchObject(catalog) + }) + it("should correct badly used comments", () => { const po = PO.parse(` #. First description diff --git a/packages/cli/src/api/formats/po.ts b/packages/cli/src/api/formats/po.ts index beb3f7433..655893c23 100644 --- a/packages/cli/src/api/formats/po.ts +++ b/packages/cli/src/api/formats/po.ts @@ -5,9 +5,13 @@ import PO from "pofile" import { joinOrigin, splitOrigin, writeFileIfChanged } from "../utils" import { CatalogType, MessageType } from "../catalog" import { CatalogFormatOptionsInternal, CatalogFormatter } from "." +import { generateMessageId } from "@lingui/message-id" type POItem = InstanceType +function isGeneratedId(id: string, message: MessageType): boolean { + return id === generateMessageId(message.message, message.context) +} function getCreateHeaders(language = "no"): PO["headers"] { return { "POT-Creation-Date": formatDate(new Date(), "yyyy-MM-dd HH:mmxxxx"), @@ -28,17 +32,34 @@ export const serialize = ( const message = catalog[id] const item = new PO.Item() - item.msgid = id - item.msgstr = [message.translation] - item.comments = message.comments || [] - // The extractedComments array may be modified in this method, so create a new array with the message's elements. - // Destructuring `undefined` is forbidden, so fallback to `[]` if the message has no extracted comments. - item.extractedComments = [...(message.extractedComments ?? [])] + // The extractedComments array may be modified in this method, + // so create a new array with the message's elements. + item.extractedComments = [...(message.extractedComments || [])] + + item.flags = (message.flags || []).reduce>( + (acc, flag) => { + acc[flag] = true + return acc + }, + {} + ) + + if (isGeneratedId(id, message)) { + item.msgid = message.message + item.flags["generated-id"] = true + // item.extractedComments.push(`lingui_id: ${id}`) + } else { + item.msgid = id + } if (message.context) { item.msgctxt = message.context } + + item.msgstr = [message.translation] + item.comments = message.comments || [] + if (options.origins !== false) { if (message.origin && options.lineNumbers === false) { item.references = message.origin.map(([path]) => path) @@ -47,12 +68,6 @@ export const serialize = ( } } item.obsolete = message.obsolete - item.flags = message.flags - ? message.flags.reduce>((acc, flag) => { - acc[flag] = true - return acc - }, {}) - : {} return postProcessItem ? postProcessItem(item, message, id) : item }) @@ -65,7 +80,7 @@ export function deserialize( return items.reduce((catalog, item) => { onItem(item) - catalog[item.msgid] = { + const message: MessageType = { translation: item.msgstr[0], extractedComments: item.extractedComments || [], comments: item.comments || [], @@ -75,6 +90,15 @@ export function deserialize( flags: Object.keys(item.flags).map((flag) => flag.trim()), } + let id = item.msgid + + // if generated id, recreate it + if (item.flags["generated-id"]) { + id = generateMessageId(item.msgid, item.msgctxt) + message.message = item.msgid + } + + catalog[id] = message return catalog }, {}) }