Skip to content

feat: support gettext msgctxt in PO import/export and UI - #3694

Merged
JanCizmar merged 6 commits into
mainfrom
jirikuchynka/po-msgctxt-v2
Jun 3, 2026
Merged

feat: support gettext msgctxt in PO import/export and UI#3694
JanCizmar merged 6 commits into
mainfrom
jirikuchynka/po-msgctxt-v2

Conversation

@Anty0

@Anty0 Anty0 commented May 26, 2026

Copy link
Copy Markdown
Member

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

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 Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds end-to-end PO msgctxt support: parse/store msgctxt, import/export via a U+0004 separator in combined key names, backend/frontend tests and example PO files, a KeyName UI component, and editor integration.

Changes

PO msgctxt end-to-end support

Layer / File(s) Summary
PO msgctxt constant and data model
backend/data/src/main/kotlin/io/tolgee/formats/po/contsants.kt, backend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.kt
Introduced PO_MSGCTXT_KEY_SEPARATOR (U+0004) and added msgctxt StringBuilder field to store parsed context.
PO parser and importer implementation
backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.kt, backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.kt, backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.kt, backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoFileProcessorTest.kt, backend/data/src/test/resources/import/po/example_msgctxt.*
PoParser parses msgctxt and tracks expectation state; PoFileProcessor builds combined key names (msgctxt + separator + msgid); tests validate joining, escapes, and header edge cases; example PO files demonstrate scenarios.
PO exporter implementation and tests
backend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.kt, backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt
PoFileExporter splits combined key names to extract and emit msgctxt before msgid; added helpers for multiline msgctxt; tests verify separation, plural fallback, and escaping.
Frontend key name utilities
webapp/src/fixtures/keyName.ts, webapp/src/fixtures/__tests__/keyName.test.ts
Added splitKeyName utility and tests to parse combined key names into optional msgctxt and required msgid, covering edge cases.
KeyName component and documentation
webapp/src/component/KeyName/KeyName.tsx, webapp/src/constants/docLinks.ts, e2e/cypress/support/dataCyType.d.ts
New KeyName component renders msgctxt inside a tooltip with a docs link; added poMsgctxt doc link and key-name-msgctxt Cypress data-cy type.
Component adoption and editor mode support
webapp/src/component/editor/Editor.tsx, webapp/src/component/ScreenshotWithLabels.tsx, webapp/src/component/activity/references/KeyReference.tsx, webapp/src/views/projects/.../*, webapp/src/ee/.../*
Editor gains 'keyName' mode with KeyNamePlugin and theme styles; multiple UI components and tooltips now use KeyName to display key names with context.
E2E testdata and specs
backend/development/.../PoMsgctxtTestData.kt, backend/development/.../PoMsgctxtE2eDataController.kt, e2e/cypress/e2e/translations/poMsgctxt.cy.ts, e2e/cypress/common/apiCalls/testData/testData.ts
Added test-data builder and internal controller; Cypress specs validate msgctxt chip rendering and paste behavior in key-name input; test-data export wired for Cypress.
Build configuration
webapp/vite.config.ts, webapp/package.json
Added CodeMirror packages to Vite resolve.dedupe and bumped @tginternal/editor version.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • JanCizmar
  • dkrizan
  • bdshadow

🐰 I stitched contexts with a tiny control sign,
From PO to UI the msgctxt did shine.
Tooltips whisper context, escaped quotes kept neat,
Keys now carry stories, small and complete.
Hop, nibble, celebrate—one separator line!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding msgctxt support to PO import/export and UI components.
Linked Issues check ✅ Passed All coding requirements from issue #3053 are met: msgctxt import with U+0004 separator [#3053], UI highlighting via KeyName component [#3053], and editor integration for editability [#3053].
Out of Scope Changes check ✅ Passed All changes directly support msgctxt functionality; Vite deduplication is necessary infrastructure for the editor integration dependency, not out-of-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jirikuchynka/po-msgctxt-v2

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt (1)

249-266: ⚡ Quick win

Consider adding test for empty msgctxt edge case.

The test suite covers msgctxt with special characters, but doesn't test the edge case where msgctxt is empty (e.g., key = "${PO_MSGCTXT_KEY_SEPARATOR}Save"). Per the PR summary, empty msgctxt should collapse to a plain key without emitting a msgctxt "" line.

🧪 Suggested test case
`@Test`
fun `does not export msgctxt when context is empty`() {
  val data = getExported(
    getExporter(
      listOf(
        translationView(
          keyId = 1,
          keyName = "${PO_MSGCTXT_KEY_SEPARATOR}Save",
          text = "Speichern",
        ),
      ),
    ),
  )
  data["de.po"].assert.doesNotContain("msgctxt")
  data["de.po"].assert.contains("msgid \"Save\"")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt`
around lines 249 - 266, Add a new unit test in PoFileExporterTest (e.g., fun
`does not export msgctxt when context is empty` ) that constructs a
translationView with keyName = PO_MSGCTXT_KEY_SEPARATOR + "Save" (use the same
getExporter/getExported helpers as other tests) and asserts the resulting
data["de.po"] doesNotContain "msgctxt" and does contain "msgid \"Save\""; this
verifies empty msgctxt collapses to a plain key instead of emitting msgctxt "".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.kt`:
- Around line 55-59: The writeMsgCtxt currently writes a msgctxt even when
getMsgCtxt() returns an empty string; update StringBuilder.writeMsgCtxt to skip
writing when msgctxt is null or empty (use a null-or-empty check like
msgctxt.isNullOrEmpty()) so empty msgctxts are filtered out and only non-empty
contexts are emitted.

In `@backend/data/src/test/resources/import/po/example_msgctxt_escapes.po`:
- Line 7: The PO fixture header contains a non-standard escaped inequality in
the Plural-Forms header ("Plural-Forms" value uses "n \!= 1"); update the header
value to use the standard expression without the backslash (e.g., "plural=(n !=
1);") so the gettext plural rule is valid; locate the header string in
example_msgctxt_escapes.po and replace the "n \!= 1" occurrence with "n != 1".

In `@webapp/src/component/KeyName/KeyName.tsx`:
- Around line 37-41: The early-return in KeyName.tsx uses if (!msgctxt) which
treats empty string ('') the same as missing context and causes combined keys
like "<separator>msgid" to render raw name; change the guard to only check for
null/undefined (e.g., msgctxt === undefined || msgctxt === null) so an empty
string is treated as present and the component can strip the separator and
render the clean msgid; update the logic around variables msgctxt and name in
the KeyName component accordingly so className and data-cy rendering remains
unchanged.
- Around line 49-54: The T component usage in KeyName (component KeyName,
element T with keyName "translations_key_msgctxt_tooltip") lacks a defaultValue
so the tooltip can be empty before Tolgee syncs; add a defaultValue prop that
matches the params tag key by using tag interpolation — e.g. set defaultValue to
something like "See <LearnMore></LearnMore> for details" (or the appropriate
copy) and keep params={{ LearnMore: <LinkReadMore url={DOCS_LINKS.poMsgctxt}/>
}} so the LearnMore tag is replaced by the LinkReadMore component.

---

Nitpick comments:
In
`@backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt`:
- Around line 249-266: Add a new unit test in PoFileExporterTest (e.g., fun
`does not export msgctxt when context is empty` ) that constructs a
translationView with keyName = PO_MSGCTXT_KEY_SEPARATOR + "Save" (use the same
getExporter/getExported helpers as other tests) and asserts the resulting
data["de.po"] doesNotContain "msgctxt" and does contain "msgid \"Save\""; this
verifies empty msgctxt collapses to a plain key instead of emitting msgctxt "".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1fe13a26-0b7c-4f62-a384-2ee3ba2e7905

📥 Commits

Reviewing files that changed from the base of the PR and between 7a85203 and 21e1831.

📒 Files selected for processing (30)
  • backend/data/src/main/kotlin/io/tolgee/formats/po/contsants.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoFileProcessorTest.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt
  • backend/data/src/test/resources/import/po/example_msgctxt.po
  • backend/data/src/test/resources/import/po/example_msgctxt_escapes.po
  • backend/data/src/test/resources/import/po/example_msgctxt_only.po
  • e2e/cypress/support/dataCyType.d.ts
  • webapp/src/component/KeyName/KeyName.tsx
  • webapp/src/component/ScreenshotWithLabels.tsx
  • webapp/src/component/activity/references/KeyReference.tsx
  • webapp/src/component/editor/Editor.tsx
  • webapp/src/constants/docLinks.ts
  • webapp/src/ee/branching/merge/components/changes/MergeKeyHeader.tsx
  • webapp/src/ee/translationMemory/components/content/TranslationMemoryEntryRow.tsx
  • webapp/src/fixtures/__tests__/keyName.test.ts
  • webapp/src/fixtures/keyName.ts
  • webapp/src/views/projects/import/component/ImportConflictsData.tsx
  • webapp/src/views/projects/import/component/ImportTranslationsDialog.tsx
  • webapp/src/views/projects/translations/KeyCellContent.tsx
  • webapp/src/views/projects/translations/KeyCreateForm/FormBody.tsx
  • webapp/src/views/projects/translations/KeyEdit/KeyGeneral.tsx
  • webapp/src/views/projects/translations/KeySingle/KeySingle.tsx
  • webapp/src/views/projects/translations/SimpleCellKey.tsx
  • webapp/src/views/projects/translations/ToolsPanel/panels/TranslationMemory/TranslationMemoryItem.tsx
  • webapp/vite.config.ts

Comment thread backend/data/src/test/resources/import/po/example_msgctxt_escapes.po Outdated
Comment thread webapp/src/component/KeyName/KeyName.tsx
Comment thread webapp/src/component/KeyName/KeyName.tsx

@JanCizmar JanCizmar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job! Thanks 👍

  • Missing e2e tests

Anty0 added a commit to tolgee/tolgee-js that referenced this pull request May 27, 2026
## Summary
- The in-context **KeyDialog** and the **ScreenshotWithLabels** tooltip
now use a new `<KeyName>` component that splits the key name on the
U+0004 separator (set by tolgee-platform when importing gettext `.po`
files with a `msgctxt`) and renders the `msgctxt` portion as a styled
chip in front of the `msgid`.
- Standalone implementation — no runtime dependency on
`@tginternal/editor` to keep this package's bundle independent of
release coordination.
- The platform-side change introducing the separator and the matching
chip visual lives in the companion tolgee-platform PR.

## Companion PRs
- tolgee-platform: tolgee/tolgee-platform#3694
- editor: tolgee/editor#8
- documentation: tolgee/documentation#1111

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added improved key name rendering with enhanced formatting that
visually distinguishes embedded metadata using dedicated styling
* Updated the UI across dialogs and tooltips to consistently leverage
the new key name display, improving visual clarity and presentation when
viewing keys with associated metadata

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tolgee/tolgee-js/pull/3523?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 -->

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/cypress/e2e/translations/poMsgctxt.cy.ts`:
- Around line 30-41: The E2E spec is using text and class selectors instead of
typed data-cy hooks; update the assertions to use only the gcy/findDcy helpers:
replace the .contains('Open') and .contains('plain.key') chains with
cy.gcy('translations-key-name').findDcy('key-name-trigger') (or the appropriate
data-cy for the trigger) to locate the row, then use
.findDcy('key-name-msgctxt') for the msgctxt element and assert visibility/text;
also replace any .find('.cm-content') and .find('.keyname-msgctxt-widget')
usages with .findDcy('cm-content') and .findDcy('keyname-msgctxt-widget')
respectively so all selectors use explicit data-cy attributes and the existing
helpers (cy.gcy / findDcy).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bef1d65d-fee3-443a-a173-9e9b2edeef3b

📥 Commits

Reviewing files that changed from the base of the PR and between 88f7ebb and de55ace.

⛔ Files ignored due to path filters (1)
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PoMsgctxtTestData.kt
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PoMsgctxtE2eDataController.kt
  • e2e/cypress/common/apiCalls/testData/testData.ts
  • e2e/cypress/e2e/translations/poMsgctxt.cy.ts
  • webapp/package.json

Comment thread e2e/cypress/e2e/translations/poMsgctxt.cy.ts
@Anty0
Anty0 force-pushed the jirikuchynka/po-msgctxt-v2 branch from de55ace to ba38517 Compare May 27, 2026 08:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
webapp/src/component/KeyName/KeyName.tsx (2)

49-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add defaultValue for the new tooltip translation key.

The <T> component is missing a defaultValue prop. Since translations_key_msgctxt_tooltip is a new translation key introduced in this PR, it requires a defaultValue to ensure the UI displays meaningful text before translations are uploaded to Tolgee. The defaultValue should use tag interpolation matching the params structure (e.g., "Context for disambiguation. <LearnMore></LearnMore>").

As per coding guidelines: Always provide defaultValue when using the T component or t() function to ensure the UI displays meaningful text before translations are uploaded to Tolgee.

💡 Proposed fix
   <T
     keyName="translations_key_msgctxt_tooltip"
+    defaultValue="Context for disambiguation. <LearnMore></LearnMore>"
     params={{
       LearnMore: <LinkReadMore url={DOCS_LINKS.poMsgctxt} />,
     }}
   />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/component/KeyName/KeyName.tsx` around lines 49 - 54, Add a
defaultValue prop to the <T> usage in KeyName.tsx for the new translation key
"translations_key_msgctxt_tooltip": update the <T
keyName="translations_key_msgctxt_tooltip" ... /> element to include
defaultValue that matches the params tag interpolation (use the same LearnMore
tag name used in params), e.g. a sentence like "Context for disambiguation.
<LearnMore></LearnMore>", so the tooltip shows meaningful text before Tolgee
translations are available; ensure the tag name in defaultValue matches the
LinkReadMore param key.

37-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle empty msgctxt without rendering the raw combined key.

The check if (!msgctxt) matches undefined, null, and empty string ''. If splitKeyName returns an empty string for msgctxt (which could happen for a malformed key starting with the separator), the component renders the raw name including the separator instead of the clean msgid.

🛡️ Proposed fix
-  if (!msgctxt) {
+  if (msgctxt === undefined) {
     return (
       <span className={className} data-cy={dataCy}>
-        {name}
+        {msgid}
       </span>
     );
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/component/KeyName/KeyName.tsx` around lines 37 - 41, The early
return in the KeyName component uses a falsy check for msgctxt which treats
empty string ('') as missing and causes rendering of the raw combined name;
change the condition to only treat null/undefined as absent (e.g., use msgctxt
== null or typeof msgctxt === 'undefined' || msgctxt === null) so that an
empty-string msgctxt returned by splitKeyName still allows the component to
render the cleaned msgid; update the conditional in KeyName (referencing
splitKeyName, msgctxt, name, and the KeyName component) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@webapp/src/component/KeyName/KeyName.tsx`:
- Around line 49-54: Add a defaultValue prop to the <T> usage in KeyName.tsx for
the new translation key "translations_key_msgctxt_tooltip": update the <T
keyName="translations_key_msgctxt_tooltip" ... /> element to include
defaultValue that matches the params tag interpolation (use the same LearnMore
tag name used in params), e.g. a sentence like "Context for disambiguation.
<LearnMore></LearnMore>", so the tooltip shows meaningful text before Tolgee
translations are available; ensure the tag name in defaultValue matches the
LinkReadMore param key.
- Around line 37-41: The early return in the KeyName component uses a falsy
check for msgctxt which treats empty string ('') as missing and causes rendering
of the raw combined name; change the condition to only treat null/undefined as
absent (e.g., use msgctxt == null or typeof msgctxt === 'undefined' || msgctxt
=== null) so that an empty-string msgctxt returned by splitKeyName still allows
the component to render the cleaned msgid; update the conditional in KeyName
(referencing splitKeyName, msgctxt, name, and the KeyName component)
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f724830-e7c1-4e3d-ab3b-00ba3b4d5cae

📥 Commits

Reviewing files that changed from the base of the PR and between de55ace and ba38517.

⛔ Files ignored due to path filters (1)
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PoMsgctxtTestData.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/contsants.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoFileProcessorTest.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt
  • backend/data/src/test/resources/import/po/example_msgctxt.po
  • backend/data/src/test/resources/import/po/example_msgctxt_escapes.po
  • backend/data/src/test/resources/import/po/example_msgctxt_only.po
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PoMsgctxtE2eDataController.kt
  • e2e/cypress/common/apiCalls/testData/testData.ts
  • e2e/cypress/e2e/translations/poMsgctxt.cy.ts
  • e2e/cypress/support/dataCyType.d.ts
  • webapp/package.json
  • webapp/src/component/KeyName/KeyName.tsx
  • webapp/src/component/ScreenshotWithLabels.tsx
  • webapp/src/component/activity/references/KeyReference.tsx
  • webapp/src/component/editor/Editor.tsx
  • webapp/src/constants/docLinks.ts
  • webapp/src/ee/branching/merge/components/changes/MergeKeyHeader.tsx
  • webapp/src/ee/translationMemory/components/content/TranslationMemoryEntryRow.tsx
  • webapp/src/fixtures/__tests__/keyName.test.ts
  • webapp/src/fixtures/keyName.ts
  • webapp/src/views/projects/import/component/ImportConflictsData.tsx
  • webapp/src/views/projects/import/component/ImportTranslationsDialog.tsx
  • webapp/src/views/projects/translations/KeyCellContent.tsx
  • webapp/src/views/projects/translations/KeyCreateForm/FormBody.tsx
  • webapp/src/views/projects/translations/KeyEdit/KeyGeneral.tsx
  • webapp/src/views/projects/translations/KeySingle/KeySingle.tsx
  • webapp/src/views/projects/translations/SimpleCellKey.tsx
  • webapp/src/views/projects/translations/ToolsPanel/panels/TranslationMemory/TranslationMemoryItem.tsx
  • webapp/vite.config.ts
✅ Files skipped from review due to trivial changes (5)
  • backend/data/src/test/resources/import/po/example_msgctxt_only.po
  • webapp/vite.config.ts
  • webapp/src/constants/docLinks.ts
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/contsants.kt
🚧 Files skipped from review as they are similar to previous changes (27)
  • webapp/src/views/projects/translations/KeyCellContent.tsx
  • webapp/src/component/activity/references/KeyReference.tsx
  • webapp/src/views/projects/import/component/ImportConflictsData.tsx
  • webapp/src/ee/branching/merge/components/changes/MergeKeyHeader.tsx
  • backend/data/src/test/resources/import/po/example_msgctxt_escapes.po
  • e2e/cypress/support/dataCyType.d.ts
  • webapp/src/views/projects/translations/KeyEdit/KeyGeneral.tsx
  • webapp/src/component/ScreenshotWithLabels.tsx
  • webapp/package.json
  • webapp/src/views/projects/translations/KeyCreateForm/FormBody.tsx
  • backend/data/src/test/resources/import/po/example_msgctxt.po
  • e2e/cypress/common/apiCalls/testData/testData.ts
  • webapp/src/views/projects/translations/ToolsPanel/panels/TranslationMemory/TranslationMemoryItem.tsx
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PoMsgctxtE2eDataController.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.kt
  • webapp/src/fixtures/keyName.ts
  • webapp/src/views/projects/import/component/ImportTranslationsDialog.tsx
  • webapp/src/fixtures/tests/keyName.test.ts
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PoMsgctxtTestData.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt
  • e2e/cypress/e2e/translations/poMsgctxt.cy.ts
  • backend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.kt
  • backend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.kt
  • webapp/src/views/projects/translations/KeySingle/KeySingle.tsx
  • webapp/src/views/projects/translations/SimpleCellKey.tsx
  • backend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.kt
  • webapp/src/component/editor/Editor.tsx

@Anty0
Anty0 requested a review from JanCizmar May 27, 2026 08:43
Anty0 and others added 6 commits June 2, 2026 15:48
Import and export of .po files now preserves the msgctxt
field. Tolgee stores the pair (msgctxt, msgid) as a single
key name using the U+0004 EOT separator (same convention
GNU Gettext uses internally in MO files), so two entries
with the same msgid but different msgctxt remain distinct
keys.

The UI renders the msgctxt portion as a styled chip in
front of the msgid in all key-name display sites and inside
the key-name editor (via a new CodeMirror mode), so display
and edit look identical.
Pulls in KeyNamePlugin, generateKeyNameStyle and the
PO_MSGCTXT_KEY_SEPARATOR constant required for the
msgctxt chip rendering.
Adds PoMsgctxtTestData with two keys (one plain, one
encoding a gettext msgctxt via the U+0004 separator) and an
e2e suite that verifies:

- the chip widget is rendered next to the msgid in the
  translations grid, and is absent for plain keys
- pasting a string containing the U+0004 separator into the
  key-name editor produces the same chip widget inside the
  editor
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Switching the StyledEditorWrapper based on `mode` rebuilt the
styled component on every mode change, which made React remount
the wrapper and tore down the CodeMirror EditorView along with
it. This broke any flow that toggled modes mid-edit (e.g.
switchToSyntaxMode in the key create dialog used by the char
limit e2e tests). Build a single wrapper that applies both sets
of styles and keep useMemo dependent only on the palette.
@Anty0
Anty0 force-pushed the jirikuchynka/po-msgctxt-v2 branch from 5af1315 to 7e283f4 Compare June 2, 2026 13:48
@JanCizmar
JanCizmar merged commit dfd7775 into main Jun 3, 2026
144 of 150 checks passed
@JanCizmar
JanCizmar deleted the jirikuchynka/po-msgctxt-v2 branch June 3, 2026 08:34
@JanCizmar

Copy link
Copy Markdown
Member

Awesome job! Thx.

TolgeeMachine added a commit that referenced this pull request Jun 3, 2026
# [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)
Anty0 added a commit to tolgee/documentation that referenced this pull request Jun 3, 2026
## Summary
- Adds a new **msgctxt and key names** section to
`platform/formats/po.mdx` explaining how Tolgee encodes the gettext
`msgctxt` field into key names using the U+0004 separator.
- Includes an example, a description of the UI chip rendering, and a
link to the GNU Gettext documentation on contexts.
- Mentions `msgctxt` in the existing Feature support paragraph.

## Companion PRs
- tolgee-platform: tolgee/tolgee-platform#3694
- tolgee-js: tolgee/tolgee-js#3523
- editor: tolgee/editor#8
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.

Store msgctxt from Gettext (.po) files

2 participants