feat: support gettext msgctxt in PO import/export and UI - #3694
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesPO msgctxt end-to-end support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 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 |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
backend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.kt (1)
249-266: ⚡ Quick winConsider 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 amsgctxt ""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
📒 Files selected for processing (30)
backend/data/src/main/kotlin/io/tolgee/formats/po/contsants.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoFileProcessorTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.ktbackend/data/src/test/resources/import/po/example_msgctxt.pobackend/data/src/test/resources/import/po/example_msgctxt_escapes.pobackend/data/src/test/resources/import/po/example_msgctxt_only.poe2e/cypress/support/dataCyType.d.tswebapp/src/component/KeyName/KeyName.tsxwebapp/src/component/ScreenshotWithLabels.tsxwebapp/src/component/activity/references/KeyReference.tsxwebapp/src/component/editor/Editor.tsxwebapp/src/constants/docLinks.tswebapp/src/ee/branching/merge/components/changes/MergeKeyHeader.tsxwebapp/src/ee/translationMemory/components/content/TranslationMemoryEntryRow.tsxwebapp/src/fixtures/__tests__/keyName.test.tswebapp/src/fixtures/keyName.tswebapp/src/views/projects/import/component/ImportConflictsData.tsxwebapp/src/views/projects/import/component/ImportTranslationsDialog.tsxwebapp/src/views/projects/translations/KeyCellContent.tsxwebapp/src/views/projects/translations/KeyCreateForm/FormBody.tsxwebapp/src/views/projects/translations/KeyEdit/KeyGeneral.tsxwebapp/src/views/projects/translations/KeySingle/KeySingle.tsxwebapp/src/views/projects/translations/SimpleCellKey.tsxwebapp/src/views/projects/translations/ToolsPanel/panels/TranslationMemory/TranslationMemoryItem.tsxwebapp/vite.config.ts
JanCizmar
left a comment
There was a problem hiding this comment.
Good job! Thanks 👍
- Missing e2e tests
## 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 --> [](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 -->
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
webapp/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PoMsgctxtTestData.ktbackend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PoMsgctxtE2eDataController.kte2e/cypress/common/apiCalls/testData/testData.tse2e/cypress/e2e/translations/poMsgctxt.cy.tswebapp/package.json
de55ace to
ba38517
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (2)
webapp/src/component/KeyName/KeyName.tsx (2)
49-54:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd
defaultValuefor the new tooltip translation key.The
<T>component is missing adefaultValueprop. Sincetranslations_key_msgctxt_tooltipis a new translation key introduced in this PR, it requires adefaultValueto ensure the UI displays meaningful text before translations are uploaded to Tolgee. ThedefaultValueshould use tag interpolation matching theparamsstructure (e.g.,"Context for disambiguation. <LearnMore></LearnMore>").As per coding guidelines: Always provide
defaultValuewhen using theTcomponent ort()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 winHandle empty
msgctxtwithout rendering the raw combined key.The check
if (!msgctxt)matchesundefined,null, and empty string''. IfsplitKeyNamereturns an empty string formsgctxt(which could happen for a malformed key starting with the separator), the component renders the rawnameincluding the separator instead of the cleanmsgid.🛡️ 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
⛔ Files ignored due to path filters (1)
webapp/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (35)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PoMsgctxtTestData.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/contsants.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/PoFileProcessor.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/PoParser.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/in/data/PoParsedTranslation.ktbackend/data/src/main/kotlin/io/tolgee/formats/po/out/PoFileExporter.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoFileProcessorTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/in/PoParserTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/po/out/PoFileExporterTest.ktbackend/data/src/test/resources/import/po/example_msgctxt.pobackend/data/src/test/resources/import/po/example_msgctxt_escapes.pobackend/data/src/test/resources/import/po/example_msgctxt_only.pobackend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PoMsgctxtE2eDataController.kte2e/cypress/common/apiCalls/testData/testData.tse2e/cypress/e2e/translations/poMsgctxt.cy.tse2e/cypress/support/dataCyType.d.tswebapp/package.jsonwebapp/src/component/KeyName/KeyName.tsxwebapp/src/component/ScreenshotWithLabels.tsxwebapp/src/component/activity/references/KeyReference.tsxwebapp/src/component/editor/Editor.tsxwebapp/src/constants/docLinks.tswebapp/src/ee/branching/merge/components/changes/MergeKeyHeader.tsxwebapp/src/ee/translationMemory/components/content/TranslationMemoryEntryRow.tsxwebapp/src/fixtures/__tests__/keyName.test.tswebapp/src/fixtures/keyName.tswebapp/src/views/projects/import/component/ImportConflictsData.tsxwebapp/src/views/projects/import/component/ImportTranslationsDialog.tsxwebapp/src/views/projects/translations/KeyCellContent.tsxwebapp/src/views/projects/translations/KeyCreateForm/FormBody.tsxwebapp/src/views/projects/translations/KeyEdit/KeyGeneral.tsxwebapp/src/views/projects/translations/KeySingle/KeySingle.tsxwebapp/src/views/projects/translations/SimpleCellKey.tsxwebapp/src/views/projects/translations/ToolsPanel/panels/TranslationMemory/TranslationMemoryItem.tsxwebapp/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
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.
5af1315 to
7e283f4
Compare
|
Awesome job! Thx. |
# [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 - 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
Supersedes #3459
Fixes #3053
Summary
msgctxtfield. 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 samemsgidbut differentmsgctxtremain distinct keys.msgctxtline back beforemsgid. Plural fallback now uses the splitmsgidinstead of the raw key name.msgidandmsgctxtto be empty, so a malformed msgctxt-only entry can't be misread as the header block. The legacyPO_MSGCTXT_NOT_SUPPORTEDfile issue is no longer emitted (the enum value stays in place for back-compat with previously stored issues).<KeyName>component renders themsgctxtportion as a styled chip in front of themsgidacross 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 adocument.titlestring.<Editor>component gains akeyNamemode that wires a new CodeMirror plugin (KeyNamePluginfrom@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.@codemirror/state,@codemirror/view,@codemirror/linttoresolve.dedupeto avoid the "multiple instances of @codemirror/state" runtime error introduced whenpreserveSymlinks: trueis combined with the linked@tginternal/editorworktree.Notes
.pofile that was imported before this change (when msgctxt was silently dropped) will create new keys for the(msgctxt, msgid)pairs instead of updating the existingmsgid-only keys. Accepted breakage — most users import once.msgctxt ""collapses to a plain key without a chip, matching gettext semantics.Companion PRs
Summary by CodeRabbit
New Features
Tests
Chores