[DataObject] [Classification Store] Fix selection modal: name/description are not translated via i18next - #3972
Conversation
Collection/Gruppe/"Nach Schlüssel gruppieren" render name/description values verbatim, but they may be translation keys in the studio domain (the same convention already supported for grid values via the Translate transformer). Resolve them through i18next in each tab's column definitions, falling back to the raw value when empty.
There was a problem hiding this comment.
Pull request overview
Adds i18next translation support for Classification Store modal labels while retaining untranslated values as fallbacks.
Changes:
- Adds a shared label translation helper.
- Translates collection, group, and key names/descriptions.
- Applies translation renderers across all selection tabs.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
translate-label.ts |
Adds shared label translation logic. |
collection-tab.tsx |
Translates collection labels. |
group-tab.tsx |
Translates group labels. |
group-by-key-tab.tsx |
Translates group and key labels. |
…lateLabel i18next treats ":" as the default namespace separator, so a plain-text label containing a colon (e.g. "Color: Blue") was split into a namespace/key pair and silently fell back to the part after the colon instead of the original text. Pass nsSeparator: false to keep such values intact.
There was a problem hiding this comment.
Verified against the i18next setup and the existing conventions:
- The global i18next init (
core/app/i18n/index.ts) setskeySeparator: falsebut leavesnsSeparatorat its:default — so the per-callnsSeparator: falseadded for Copilot's colon finding is exactly the right (and minimal) guard: plain-text values likeColor: Bluestay intact, and dotted keys were already safe globally. - Behaviour matches the classic admin UI, which also runs CS collection/group/key names & descriptions through the translator, and mirrors the existing grid-value
Translatetransformer convention — untranslated values fall back to the raw text (i18next returns the key on a miss). - All three tabs (collection / group / group-by-key) are covered; the helper lives at a sensible boundary under
classification-store/utils. - Copilot's single finding (colon namespace parsing) is addressed; I re-requested a Copilot pass on the follow-up commit to confirm. CI green, mergeable.
I added Fixes pimcore/platform-version#305 to the description and set the 2026.2.7 milestone.
…lation Combines the useMemo'd, sized column definitions from pimcore#3971 with the translateLabel cell renderers from this PR; build-dist zip kept from this branch - the frontend build bot regenerates it.
|
There was a problem hiding this comment.
@albertmueller thank you for this contribution, and for the unusually thorough issue and PR description — the reproduction steps and root-cause pointer saved a lot of time, and the screenshots made the before/after obvious. The gap is real: I confirmed it on origin/2026.2 @ 514f6f6ad, and it isn't a regression — the tabs have never had a cell renderer, going back to when the modal landed in 232d77b57 (#1590). CI is fully green, and the fix is aimed at the right layer.
The direction is right, so this is about finishing it rather than rethinking it. Four items, the first two substantive:
1. Rewrite the helper and move it to core/utils/translate-label.ts. Details and the suggested body are in the inline comment. In short: guard with i18n.exists() so the helper stops feeding user data into i18next's saveMissing path (which POSTs translationCreate and inserts rows into translations_studio), use isNonEmptyString/toDisplayString from core/utils/type-utils.ts so whitespace-only values don't become keys, and take no t parameter since TFunction has no exists(). Please keep it out of the @sdk/utils barrel for now — no need to add public SDK surface in a bugfix.
2. Update the 7 call sites in collection-tab.tsx (2), group-tab.tsx (2) and group-by-key-tab.tsx (3):
cell: (info) => translateLabel(info.getValue())importing from @Pimcore/utils/translate-label.
3. Use the same helper in base-view.tsx:49:
const translatedTitle = translateLabel(props.title)Without this the PR leaves the same value rendering two different ways. classification-store-item.tsx:82 passes groupLayout?.name into BaseView, which already translates it at :49 — but without the nsSeparator: false guard. So after this PR a group named Color: Blue renders correctly in the modal and as Blue in the editor's group panel header, which is exactly the bug Copilot flagged, just one layer over.
The same isNonEmptyString(x) ? t(x) : x idiom exists in six more places (panel.tsx:42, tabpanel.tsx:101, field-label.tsx:52, accordion.tsx, object-tabpanel.tsx, calculated-value/.../label.tsx:29). Those don't render Classification Store data, so please leave them alone here — we'll convert them in a separate ticket. Sonar's 22.2% duplication on new code is pointing at the same underlying thing.
4. Add core/utils/translate-label.test.ts. classification-store/utils/group-value.test.ts is a good template for the folder conventions, and Sonar currently reports 0% coverage on new code. The helper is pure, so four cases cover it:
| input | expected |
|---|---|
| key with a matching translation | translated text |
| key with no translation | raw value unchanged |
'Color: Blue' |
'Color: Blue' (not ' Blue') |
'', ' ', null, undefined |
'' |
Thanks again — happy to re-review as soon as these are in.
| export const translateLabel = (t: TFunction, value: unknown): string => { | ||
| const stringValue = isNil(value) ? '' : String(value) | ||
|
|
||
| return isEmpty(stringValue) ? stringValue : t(stringValue, { nsSeparator: false }) | ||
| } |
There was a problem hiding this comment.
import i18n from 'i18next'
import { isNonEmptyString, toDisplayString } from './type-utils'
const TRANSLATE_OPTIONS = { nsSeparator: false } as const
export const translateLabel = (value: unknown): string => {
if (!isNonEmptyString(value)) {
return toDisplayString(value)
}
return i18n.exists(value, TRANSLATE_OPTIONS) ? i18n.t(value, TRANSLATE_OPTIONS) : value
}


Description
The ClassificationStore selection modal's "Collection", "Gruppe"/"Group" and "Nach Schlüssel
gruppieren"/"Group by key" tabs render the
name/description(andgroupName/keyName/keyDescription) values verbatim as returned by the API. If a project stores translation keysin these fields instead of display-ready text — the same convention already supported for grid
values via the
Translategrid transformer and the Twigtransfilter — the raw key is shownto the user instead of a localized label.
Steps to reproduce
name/descriptionis atranslation key in the
studiodomain (e.g.myApp.collection.foo.name), with a matchingtranslation entry for at least one locale.
classificationstorefield.Expected behavior
The Name/Description columns resolve the value through i18next (domain
studio, which theStudio frontend already loads in full via
TranslationController/getAllTranslationsByLocale),consistent with how other Studio UI strings are translated. If no matching translation key
exists, the raw value is shown unchanged (i18next's default fallback), so this is fully
backward-compatible for installations that store plain text.
Actual behavior
The raw key string is always shown, regardless of the current backend language.
Root cause
CollectionTab,GroupTabandGroupByKeyTab(
src/core/modules/element/dynamic-types/definitions/objects/data-related/components/classification-store/components/classification-store-modal/tabs/)define their
name/description/groupName/keyName/keyDescriptioncolumns without a customcellrenderer, so TanStack's default cell just prints the raw value — it is never passed throught().Environment
Fixes pimcore/platform-version#305
A fix is ready: adds a
cellrenderer per label column that resolves the value via i18next(new helper
translateLabel()), falling back to the raw value for empty strings. PR will followfrom branch
fix/classification-store-modal-label-translation.Fixed state:

Fixes pimcore/platform-version#305