Skip to content

feat: define strings with generated ids using /* i18n-s */ comments#2586

Open
MurzNN wants to merge 1 commit into
lingui:mainfrom
MurzNN:feature/define-strings-as-i18n-comments
Open

feat: define strings with generated ids using /* i18n-s */ comments#2586
MurzNN wants to merge 1 commit into
lingui:mainfrom
MurzNN:feature/define-strings-as-i18n-comments

Conversation

@MurzNN

@MurzNN MurzNN commented Jun 28, 2026

Copy link
Copy Markdown

Description

Adds a new /* i18n-s */ comment marker for extracting bare string literals with generated message IDs.

This is the first step in the epic to allow using Lingui in pure JS scripts, without macros at all. The next step is introduce a function that accepts the string and the variables, and converts the string to the generated id on the fly, an example of such function is here: #2468 (comment)

A use case for this is different scripts that should be in purge JS or TS without macro usage. There are many of them in my project, in addition to the main app, that supports macros pretty well. Just need to add support for translating strings to such scripts, to not use in them completely another translation package, but reuse the same approach.

Lingui already supports explicit message marking via /* i18n */ on string literals, where the literal value becomes the message ID and the PO formatter flags it as js-lingui-explicit-id. There was no comment-based equivalent for the default generated-ID workflow — users had to use macros (msg, defineMessage, etc.) instead.

With /* i18n-s */, a plain string can be marked for extraction while keeping standard generated-ID behavior:

const label = /* i18n-s */ 'Save'
// → id: generateMessageId("Save"), message: "Save"

This produces a regular catalog entry (no js-lingui-explicit-id flag), consistent with macro-extracted messages. The existing /* i18n */ behavior is unchanged.

Implementation is in @lingui/babel-plugin-extract-messages: comment detection was refactored into a shared helper with separate mark sets for explicit (i18n) and string (i18n-s) annotations. The StringLiteral visitor branches on comment type — i18n-s uses generateMessageId() from @lingui/message-utils. All standard comment formats are supported (/* i18n-s */, /*i18n-s*/, /** i18n-s */, /**i18n-s*/).

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Examples update

Fixes #2468 (first part)

Checklist

  • I have read the CONTRIBUTING and CODE_OF_CONDUCT docs
  • I have added tests that prove my fix is effective or that my feature works
  • I have added the necessary documentation (if appropriate)

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
js-lingui Ready Ready Preview Jun 28, 2026 4:25pm

Request Review

@MurzNN

MurzNN commented Jun 28, 2026

Copy link
Copy Markdown
Author

Please review the implementation and the overall idea. If it is good, I will extend the documentation and examples.

The key i18n-s (i18n string) is negotiable, we can replace it to some better option.

P.S. I also want to add a config option that enables adding strings with /* i18n */ as strings with generated id too (a global switch).

@MurzNN

MurzNN commented Jun 28, 2026

Copy link
Copy Markdown
Author

One of the examples on using this approach is easy defining a list of options, and displaying the translated values in pure JS using function ts() ("Translate string", source is here) like this:

const OfferStatuses = {
  proposed: /* i18n */ 'Proposed',
  shortlisted: /* i18n */ 'Shortlisted',
  accepted: /* i18n */ 'Accepted',
  declined: /* i18n */ 'Declined',
}

console.log(ts(OfferStatuses['proposed']))

@timofei-iatsenko

Copy link
Copy Markdown
Collaborator

@MurzNN hello, thanks for attempting solving this. Actually i see this diffrently. Almost all Lingui API could be implemented without a transform macro saving the same methods signature. I would like to go this direction.

We can chat in discord more about this where i can share what was already attempted, what was left and think on a general feature design. Basically most popular api methods:

t`Hello` // => "Cześć"
t`Hello {{username}}` // => "Cześć Timofei" (note the double curly braces)

Don't need a macro to work. Macro will make them more lightweight by dropping original message, but not required.

defineMessage also could be implemented without a transform, they same way as t above

msg`Hello` // => {id: "xyz123",  message: "Hello"}

But for defineMesage i have a broader plan to make it more type safe for parameters defined in it and that will require some changes in the api, because of typescript limitation:

msg(`Hello {username}`) // => {id: "xyz123",  message: "Hello {username}"}, type: MessageDescriptor<{username: string}>

Let's discuss this in the discord contribution channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants