Skip to content

Commit 25b3bc6

Browse files
authored
feat: adds custom prefix support for gettext po (#2004)
1 parent 9699ade commit 25b3bc6

File tree

5 files changed

+227
-11
lines changed

5 files changed

+227
-11
lines changed

packages/format-po-gettext/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
> **Warning**
1515
> This formatter is made for compatibility with translation management systems, which do not support ICU expressions in PO files.
16-
>
16+
>
1717
> It does not support all features of LinguiJS and should be carefully considered over other formats.
1818
>
1919
> Not supported features (native gettext doesn't support this):
@@ -72,10 +72,17 @@ export type PoGettextFormatterOptions = {
7272

7373
/**
7474
* Disable warning about unsupported `Select` feature encountered in catalogs
75-
*
75+
*
7676
* @default false
7777
*/
7878
disableSelectWarning?: boolean
79+
80+
/**
81+
* Overrides the default prefix for icu and plural comments in the final PO catalog.
82+
*
83+
* @default "js-lingui:"
84+
*/
85+
customICUPrefix?: string
7986
}
8087
```
8188

packages/format-po-gettext/src/__snapshots__/po-gettext.test.ts.snap

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,110 @@ exports[`po-gettext format should convert gettext plurals to ICU plural messages
180180
},
181181
}
182182
`;
183+
184+
exports[`po-gettext format using custom prefix handles custom prefix 1`] = `
185+
msgid ""
186+
msgstr ""
187+
"POT-Creation-Date: 2018-08-27 10:00+0000\\n"
188+
"MIME-Version: 1.0\\n"
189+
"Content-Type: text/plain; charset=utf-8\\n"
190+
"Content-Transfer-Encoding: 8bit\\n"
191+
"X-Generator: @lingui/cli\\n"
192+
"Language: en\\n"
193+
"Project-Id-Version: \\n"
194+
"Report-Msgid-Bugs-To: \\n"
195+
"PO-Revision-Date: \\n"
196+
"Last-Translator: \\n"
197+
"Language-Team: \\n"
198+
"Plural-Forms: \\n"
199+
200+
#. This is a comment by the developers about how the content must be localized.
201+
#. js-lingui-explicit-id
202+
#. custom-prefix:pluralize_on=someCount
203+
msgid "message_with_id"
204+
msgid_plural "message_with_id_plural"
205+
msgstr[0] "Singular case with id"
206+
msgstr[1] "Case number {someCount} with id"
207+
208+
#. custom-prefix:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
209+
msgid "Singular case"
210+
msgid_plural "Case number {anotherCount}"
211+
msgstr[0] "Singular case"
212+
msgstr[1] "Case number {anotherCount}"
213+
214+
`;
215+
216+
exports[`po-gettext format using custom prefix warns and falls back to using count if prefix is not found 1`] = `
217+
{
218+
lO3l+X: {
219+
comments: [
220+
js-lingui:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount,
221+
],
222+
context: null,
223+
extra: {
224+
flags: [],
225+
translatorComments: [],
226+
},
227+
message: Singular case,
228+
obsolete: false,
229+
origin: [],
230+
translation: {count, plural, one {Singular case} other {Case number {anotherCount}}},
231+
},
232+
maCaRp: {
233+
comments: [
234+
js-lingui:icu=%7Bcount%2C+plural%2C+one+%7BSingular%7D+other+%7BPlural%7D%7D&pluralize_on=count,
235+
],
236+
context: null,
237+
extra: {
238+
flags: [],
239+
translatorComments: [],
240+
},
241+
message: Singular,
242+
obsolete: false,
243+
origin: [],
244+
translation: ,
245+
},
246+
message_with_id: {
247+
comments: [
248+
js-lingui:pluralize_on=someCount,
249+
js-lingui-explicit-id,
250+
],
251+
context: null,
252+
extra: {
253+
flags: [],
254+
translatorComments: [],
255+
},
256+
obsolete: false,
257+
origin: [],
258+
translation: {count, plural, one {Singular case} other {Case number {someCount}}},
259+
},
260+
message_with_id_but_without_translation: {
261+
comments: [
262+
Comment made by the developers.,
263+
js-lingui:pluralize_on=count,
264+
js-lingui-explicit-id,
265+
],
266+
context: null,
267+
extra: {
268+
flags: [],
269+
translatorComments: [],
270+
},
271+
obsolete: false,
272+
origin: [],
273+
translation: ,
274+
},
275+
static: {
276+
comments: [
277+
js-lingui-explicit-id,
278+
],
279+
context: null,
280+
extra: {
281+
flags: [],
282+
translatorComments: [],
283+
},
284+
obsolete: false,
285+
origin: [],
286+
translation: Static message,
287+
},
288+
}
289+
`;

packages/format-po-gettext/src/po-gettext.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,78 @@ msgstr[2] "# dní"
230230

231231
expect(catalog).toMatchSnapshot()
232232
})
233+
234+
describe("using custom prefix", () => {
235+
it("parses plurals correctly", () => {
236+
const defaultProfile = fs
237+
.readFileSync(path.join(__dirname, "fixtures/messages_plural.po"))
238+
.toString()
239+
const customProfile = defaultProfile.replace(
240+
/js-lingui:/g,
241+
"custom-prefix:"
242+
)
243+
244+
const defaultPrefix = createFormat()
245+
const customPrefix = createFormat({ customICUPrefix: "custom-prefix:" })
246+
247+
const defaultCatalog = defaultPrefix.parse(
248+
defaultProfile,
249+
defaultParseCtx
250+
)
251+
const customCatalog = customPrefix.parse(customProfile, defaultParseCtx)
252+
253+
expect(defaultCatalog).toEqual(customCatalog)
254+
})
255+
256+
it("warns and falls back to using count if prefix is not found", () => {
257+
const defaultProfile = fs
258+
.readFileSync(path.join(__dirname, "fixtures/messages_plural.po"))
259+
.toString()
260+
261+
const usingInvalidPrefix = createFormat({
262+
customICUPrefix: "invalid-prefix:",
263+
})
264+
mockConsole((console) => {
265+
const catalog = usingInvalidPrefix.parse(
266+
defaultProfile,
267+
defaultParseCtx
268+
)
269+
expect(console.warn).toHaveBeenCalledWith(
270+
expect.stringContaining(
271+
"should be stored in a comment starting with"
272+
),
273+
expect.anything()
274+
)
275+
expect(catalog).toMatchSnapshot()
276+
})
277+
})
278+
279+
it("handles custom prefix", () => {
280+
const format = createFormat({ customICUPrefix: "custom-prefix:" })
281+
282+
const catalog: CatalogType = {
283+
message_with_id: {
284+
message:
285+
"{someCount, plural, one {Singular case with id\
286+
and linebreak} other {Case number {someCount} with id}}",
287+
translation:
288+
"{someCount, plural, one {Singular case with id} other {Case number {someCount} with id}}",
289+
comments: [
290+
"This is a comment by the developers about how the content must be localized.",
291+
"js-lingui-explicit-id",
292+
],
293+
},
294+
WGI12K: {
295+
message:
296+
"{anotherCount, plural, one {Singular case} other {Case number {anotherCount}}}",
297+
translation:
298+
"{anotherCount, plural, one {Singular case} other {Case number {anotherCount}}}",
299+
},
300+
}
301+
302+
const pofile = format.serialize(catalog, defaultSerializeCtx)
303+
304+
expect(pofile).toMatchSnapshot()
305+
})
306+
})
233307
})

packages/format-po-gettext/src/po-gettext.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type POItem = InstanceType<typeof PO.Item>
1313

1414
export type PoGettextFormatterOptions = PoFormatterOptions & {
1515
disableSelectWarning?: boolean
16+
customICUPrefix?: string
1617
}
1718

1819
// Attempts to turn a single tokenized ICU plural case back into a string.
@@ -40,7 +41,7 @@ const ICU_SELECT_REGEX = /^{.*, select(Ordinal)?, .*}$/
4041
const LINE_ENDINGS = /\r?\n/g
4142

4243
// Prefix that is used to identitify context information used by this module in PO's "extracted comments".
43-
const CTX_PREFIX = "js-lingui:"
44+
const DEFAULT_CTX_PREFIX = "js-lingui:"
4445

4546
function serializePlurals(
4647
item: POItem,
@@ -52,6 +53,7 @@ function serializePlurals(
5253
// Depending on whether custom ids are used by the developer, the (potential plural) "original", untranslated ICU
5354
// message can be found in `message.message` or in the item's `key` itself.
5455
const icuMessage = message.message
56+
const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX
5557

5658
if (!icuMessage) {
5759
return item
@@ -99,7 +101,7 @@ function serializePlurals(
99101
}
100102

101103
ctx.sort()
102-
item.extractedComments.push(CTX_PREFIX + ctx.toString())
104+
item.extractedComments.push(ctxPrefix + ctx.toString())
103105

104106
// If there is a translated value, parse that instead of the original message to prevent overriding localized
105107
// content with the original message. If there is no translated value, don't touch msgstr, since marking item as
@@ -161,7 +163,8 @@ const getPluralCases = (lang: string): string[] | undefined => {
161163
const convertPluralsToICU = (
162164
item: POItem,
163165
pluralForms: string[],
164-
lang: string
166+
lang: string,
167+
ctxPrefix: string = DEFAULT_CTX_PREFIX
165168
) => {
166169
const translationCount = item.msgstr.length
167170
const messageKey = item.msgid
@@ -181,13 +184,13 @@ const convertPluralsToICU = (
181184
}
182185

183186
const contextComment = item.extractedComments
184-
.find((comment) => comment.startsWith(CTX_PREFIX))
185-
?.substr(CTX_PREFIX.length)
187+
.find((comment) => comment.startsWith(ctxPrefix))
188+
?.substring(ctxPrefix.length)
186189
const ctx = new URLSearchParams(contextComment)
187190

188191
if (contextComment != null) {
189192
item.extractedComments = item.extractedComments.filter(
190-
(comment) => !comment.startsWith(CTX_PREFIX)
193+
(comment) => !comment.startsWith(ctxPrefix)
191194
)
192195
}
193196

@@ -229,7 +232,7 @@ const convertPluralsToICU = (
229232
let pluralizeOn = ctx.get("pluralize_on")
230233
if (!pluralizeOn) {
231234
console.warn(
232-
`Unable to determine plural placeholder name for item with key "%s" in language "${lang}" (should be stored in a comment starting with "#. ${CTX_PREFIX}"), assuming "count".`,
235+
`Unable to determine plural placeholder name for item with key "%s" in language "${lang}" (should be stored in a comment starting with "#. ${ctxPrefix}"), assuming "count".`,
233236
messageKey
234237
)
235238
pluralizeOn = "count"
@@ -262,7 +265,12 @@ export function formatter(
262265
let pluralForms = getPluralCases(po.headers.Language)
263266

264267
po.items.forEach((item) => {
265-
convertPluralsToICU(item, pluralForms, po.headers.Language)
268+
convertPluralsToICU(
269+
item,
270+
pluralForms,
271+
po.headers.Language,
272+
options.customICUPrefix
273+
)
266274
})
267275

268276
return formatter.parse(po.toString(), ctx) as CatalogType

website/docs/ref/catalog-formats.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export default {
146146

147147
### Configuration {#po-gettext-configuration}
148148

149-
PO Gettext formatter accepts the following options:
149+
The PO Gettext formatter accepts the following options:
150150

151151
```ts
152152
export type PoGettextFormatterOptions = {
@@ -170,6 +170,13 @@ export type PoGettextFormatterOptions = {
170170
* @default false
171171
*/
172172
disableSelectWarning?: boolean;
173+
174+
/**
175+
* Overrides the default prefix for icu and plural comments in the final PO catalog.
176+
*
177+
* @default "js-lingui:"
178+
*/
179+
customICUPrefix?: string;
173180
};
174181
```
175182

@@ -203,6 +210,19 @@ With this format, plural messages are exported in the following ways, depending
203210

204211
Note how `msgid` and `msgid_plural` were extracted from the original message.
205212

213+
- Message **with a custom comment prefix**.
214+
215+
Some TMS might modify the ICU comment by attempting to split lines to be 80 characters or less, or have trouble reading lingui comments because of the `js-lingui:` prefix. To change the prefix, set `customICUPrefix` to modify the prefix for ICU comments.
216+
217+
```po
218+
# with default prefix
219+
#. js-
220+
#. lingui:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
221+
222+
# customICUPrefix = jsi18n:
223+
#. jsi18n:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
224+
```
225+
206226
### Limitations {#po-gettext-limitations}
207227

208228
This format comes with several caveats and should only be used when using ICU plurals in PO files is not an option:

0 commit comments

Comments
 (0)