Skip to content

[DataObject] [Classification Store] Fix selection modal: name/description are not translated via i18next - #3972

Open
albertmueller wants to merge 6 commits into
pimcore:2026.2from
albertmueller:fix/classification-store-modal-label-translation
Open

[DataObject] [Classification Store] Fix selection modal: name/description are not translated via i18next#3972
albertmueller wants to merge 6 commits into
pimcore:2026.2from
albertmueller:fix/classification-store-modal-label-translation

Conversation

@albertmueller

@albertmueller albertmueller commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

The ClassificationStore selection modal's "Collection", "Gruppe"/"Group" and "Nach Schlüssel
gruppieren"/"Group by key" tabs render the name/description (and groupName/keyName/
keyDescription) values verbatim as returned by the API. If a project stores translation keys
in these fields instead of display-ready text — the same convention already supported for grid
values via the Translate grid transformer and the Twig trans filter — the raw key is shown
to the user instead of a localized label.

Screenshot 2026-08-07 085247

Steps to reproduce

  1. Configure a ClassificationStore collection/group/key whose name/description is a
    translation key in the studio domain (e.g. myApp.collection.foo.name), with a matching
    translation entry for at least one locale.
  2. Open the ClassificationStore selection modal for a classificationstore field.
  3. Observe the "Name"/"Beschreibung" columns in any of the three tabs.

Expected behavior

The Name/Description columns resolve the value through i18next (domain studio, which the
Studio 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, GroupTab and GroupByKeyTab
(src/core/modules/element/dynamic-types/definitions/objects/data-related/components/classification-store/components/classification-store-modal/tabs/)
define their name/description/groupName/keyName/keyDescription columns without a custom
cell renderer, so TanStack's default cell just prints the raw value — it is never passed through
t().

Environment

  • pimcore/studio-ui-bundle 2026.2.x
  • Pimcore 2026

Fixes pimcore/platform-version#305

A fix is ready: adds a cell renderer per label column that resolves the value via i18next
(new helper translateLabel()), falling back to the raw value for empty strings. PR will follow
from branch fix/classification-store-modal-label-translation.

Fixed state:
Screenshot 2026-08-07 093606

Fixes pimcore/platform-version#305

albertmueller and others added 2 commits August 7, 2026 09:02
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.
Copilot AI balanced review requested due to automatic review settings August 7, 2026 08:01
@pimcore-deployments
pimcore-deployments marked this pull request as draft August 7, 2026 08:02

Copilot AI 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.

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.

@ValeriaMaltseva
ValeriaMaltseva self-requested a review August 7, 2026 08:07
…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.
@albertmueller
albertmueller marked this pull request as ready for review August 7, 2026 08:17
@kingjia90 kingjia90 added this to the 2026.2.7 milestone Aug 20, 2026

@kingjia90 kingjia90 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.

Verified against the i18next setup and the existing conventions:

  • The global i18next init (core/app/i18n/index.ts) sets keySeparator: false but leaves nsSeparator at its : default — so the per-call nsSeparator: false added for Copilot's colon finding is exactly the right (and minimal) guard: plain-text values like Color: Blue stay 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 Translate transformer 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.

@kingjia90 kingjia90 self-assigned this Aug 20, 2026
kingjia90 and others added 2 commits August 20, 2026 17:13
…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.
@sonarqubecloud

Copy link
Copy Markdown

@ValeriaMaltseva ValeriaMaltseva removed this from the 2026.2.7 milestone Aug 20, 2026
@kingjia90 kingjia90 modified the milestone: 2026.2.7 Aug 20, 2026

@ValeriaMaltseva ValeriaMaltseva 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.

@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.

Comment on lines +23 to +27
export const translateLabel = (t: TFunction, value: unknown): string => {
const stringValue = isNil(value) ? '' : String(value)

return isEmpty(stringValue) ? stringValue : t(stringValue, { nsSeparator: false })
}

@ValeriaMaltseva ValeriaMaltseva Aug 21, 2026

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.

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
}

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.

5 participants