feat: store msgctxt from Gettext (.po) files - #3459
Conversation
Import msgctxt as part of the key name using the GNU gettext standard EOT separator (\u0004), store the raw value in custom key metadata for reliable round-trip export. Display context visually in the translations UI with a pipe separator. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR implements support for storing and handling the Changes
Sequence Diagram(s)sequenceDiagram
participant PO as PO File
participant Parser as PoParser
participant Model as PoParsedTranslation
participant Processor as PoFileProcessor
participant Storage as Translation<br/>Storage
PO->>Parser: Read msgctxt field
Parser->>Parser: Set expectMsgCtxt flag
Parser->>Model: Append to msgctxt StringBuilder
PO->>Parser: Read msgid field
Parser->>Model: Store msgid
PO->>Parser: Read msgstr field
Parser->>Model: Store msgstr
Processor->>Model: Retrieve msgctxt & msgid
Processor->>Processor: buildKeyName()<br/>Result: msgctxt\u0004msgid
Processor->>Storage: Store key with<br/>msgctxt metadata
Storage->>Storage: msgctxt field =<br/>PO_FILE_MSG_CTXT_CUSTOM_KEY
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
Q: Why do you store it also in the meta field? |
|
This PR is stale because it has been open for 30 days with no activity. |
|
Are we able to progress with this? Very important feature and actually a blocker for those who use contexts |
|
Hi, @sybbear! ^^ |
|
@Anty0 Ok, hopefully it will be implemented soon :) |
|
This PR is stale because it has been open for 30 days with no activity. |
|
This PR was closed because it has been inactive for 14 days since being marked as stale. |
|
Superseeded by #3694 |
Supersedes #3459 Fixes #3053 ## Summary - **Import:** the PO parser now captures the optional `msgctxt` field. On import, Tolgee stores the pair `(msgctxt, msgid)` as a single key name using the U+0004 EOT separator — the same convention GNU Gettext uses internally in MO files — so two entries with the same `msgid` but different `msgctxt` remain distinct keys. - **Export:** the PO exporter splits the key name on U+0004 and writes the `msgctxt` line back before `msgid`. Plural fallback now uses the split `msgid` instead of the raw key name. - **Parser cleanup:** header detection now requires both `msgid` and `msgctxt` to be empty, so a malformed msgctxt-only entry can't be misread as the header block. The legacy `PO_MSGCTXT_NOT_SUPPORTED` file issue is no longer emitted (the enum value stays in place for back-compat with previously stored issues). - **UI display:** a new `<KeyName>` component renders the `msgctxt` portion as a styled chip in front of the `msgid` across all key-name display sites (translations grid, simple/list view, single-key page breadcrumb, activity log, import dialogs, translation-memory rows and tooltips, branching/merge header, screenshot label tooltips). The window title intentionally still shows the raw key — the chip cannot render in a `document.title` string. - **UI edit:** the `<Editor>` component gains a `keyName` mode that wires a new CodeMirror plugin (`KeyNamePlugin` from `@tginternal/editor`) decorating the U+0004 with an atomic widget. Display and edit are now visually identical — the previous substitution-based workaround (`keyNameForEditing`/`keyNameFromEdited` + `␄` visible-char) is removed. - **Vite config:** added `@codemirror/state`, `@codemirror/view`, `@codemirror/lint` to `resolve.dedupe` to avoid the "multiple instances of @codemirror/state" runtime error introduced when `preserveSymlinks: true` is combined with the linked `@tginternal/editor` worktree. ## Notes - Re-importing a `.po` file that was imported before this change (when msgctxt was silently dropped) will create new keys for the `(msgctxt, msgid)` pairs instead of updating the existing `msgid`-only keys. Accepted breakage — most users import once. - An empty `msgctxt ""` collapses to a plain key without a chip, matching gettext semantics. ## Companion PRs - editor: tolgee/editor#8 - tolgee-js: tolgee/tolgee-js#3523 - documentation: tolgee/documentation#1111 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Full PO msgctxt support: import/export preserves and uses context to disambiguate identical strings. * Key display shows visual context indicators with a tooltip linking to docs via a new KeyName component. * Key editor gains a new "keyName" mode for viewing/editing keys with context. * Import ignores stray context-only entries to avoid header mis-parsing. * **Tests** * Added unit and end-to-end tests for msgctxt parsing, escaping, import/export, fixtures and UI behavior. * **Chores** * Editor package updated. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/tolgee/tolgee-platform/pull/3694?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
# [3.197.0](v3.196.1...v3.197.0) (2026-06-03) ### Bug Fixes * bump postgres base image 13.21 → 13.23 ([#3717](#3717)) ([7e45c07](7e45c07)) ### Features * hide QA recheck batch operation from non-privileged users ([#3703](#3703)) ([3dcd142](3dcd142)) * support gettext msgctxt in PO import/export and UI ([#3694](#3694)) ([dfd7775](dfd7775)), closes [#3459](#3459) [#3053](#3053)
Summary
msgctxtfrom PO files instead of ignoring it with a warningmsgctxt+\u0004(EOT) separator +msgidas the key name, following the GNU gettext standardmsgctxtin key custom metadata (_poFileMsgCtxt)msgctxtline beforemsgidwhen the custom value is present|separatorCloses #3053
Summary by CodeRabbit
New Features