一键导入
pimcore-studio-ui-i18n
Internationalization and translation system in Pimcore Studio UI - useTranslation hook, translation keys, and localization
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Internationalization and translation system in Pimcore Studio UI - useTranslation hook, translation keys, and localization
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Use when creating a new color theme or token set for Pimcore Studio UI (a DynamicTypeThemeAbstract subclass registered in the ThemeRegistry), including choosing an extends chain, filling AntD color tokens, seeding the boot palette, and picking colors that meet WCAG AAA contrast
Building filters in Pimcore Studio UI - the declarative defineFilter framework for filter panels and sidebars (createFiltersStore, FiltersRenderer, useFilterQuery) and custom field-filter dynamic types for per-column listing filters (DynamicTypeFieldFilterAbstract, DynamicTypeFieldFilterRegistry). Use this whenever building, customizing, or extending any kind of filtering, filter panel, filter sidebar, or search/filter UI, or adding a new filter type to a listing or data view - even when the user only says "add filtering", "let users filter by X", "filter this list", or "build a filter sidebar".
Building listings in Pimcore Studio UI using the ListingBuilder decorator pattern - sorting, paging, filtering, inline editing, and custom decorators
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
Pimcore Studio migration and review checklist — 34-point verification checklist, anti-patterns to avoid, and reference base classes for validating Studio code completeness
Pimcore Studio PHP code style rules — strict types, class modifiers, final/readonly patterns, formatting (120 chars), named arguments, imports, PHPDoc, @throws documentation, and constructor promotion
基于 SOC 职业分类
| name | pimcore-studio-ui-i18n |
| description | Internationalization and translation system in Pimcore Studio UI - useTranslation hook, translation keys, and localization |
| metadata | {"audience":"pimcore-developers","focus":"i18n-translations"} |
Internationalization (i18n) and translation system in Pimcore Studio UI:
Use this when:
BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). For core development (@sdk/*, @Pimcore/*), see the referenced file.
Note:
useTranslationis re-exported from@pimcore/studio-ui-bundle/app. Core code can also import directly fromreact-i18next. The hook at@Pimcore/modules/translations/hooks/use-translationis a different hook for CRUD operations on translation entities.
The useTranslation hook provides access to the translation function and current language.
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const MyComponent = () => {
const { t } = useTranslation()
return (
<div>
<h1>{t('my-feature.title')}</h1>
<Button>{t('save')}</Button>
</div>
)
}
const { t, i18n } = useTranslation()
// t - Translation function
// i18n - i18next instance (rarely needed directly)
ALWAYS use flat dot notation in YAML files. NEVER use nested structures!
# ✅ DO - Flat dot notation (CORRECT)
personalization.target-groups: Target Groups
personalization.target-group.add: Add Target Group
personalization.target-group.update.success: Target group updated successfully
personalization.target-group.update.error: Failed to update target group
personalization.target-group.configuration.name: Name
personalization.target-group.configuration.description: Description
# ❌ DON'T - Nested structure (WRONG!)
personalization:
target-groups: Target Groups
target-group:
add: Add Target Group
update:
success: Target group updated successfully
Every translation key MUST have an English translation in studio.en.yaml!
studio.en.yaml in your bundlet('translation.key')// ❌ DON'T - Hardcoded text
<Button>Save</Button>
<h1>Target Groups</h1>
// ✅ DO - Always use translation keys
<Button>{t('save')}</Button>
<h1>{t('personalization.target-groups')}</h1>
Translation keys follow a hierarchical structure using dot notation:
{bundle}.{module}.{component}.{specific-key}
This is the recommended convention for new bundles. However, some existing bundles use different conventions (e.g., snake_case, camelCase). When working with an existing bundle, follow its existing convention rather than imposing this pattern.
# File: studio-ui-bundle/translations/studio.en.yaml
# Common actions
save: Save
delete: Delete
cancel: Cancel
refresh: Refresh
new: New
search: Search
# Form labels
form.label.new-item: New Item
form.validation.required: This field is required
# Navigation
navigation.quick-access: Quick Access
navigation.data-management: Data Management
# Element operations
element.delete.confirmation.title: Delete Element
element.delete.confirmation.text: Are you sure you want to delete this element?
element.tree.copy-success-description: '{{elementType}} "{{name}}" copied to clipboard'
# Toolbar
toolbar.save: Save
toolbar.publish: Publish
# File: personalization-bundle/translations/studio.en.yaml
# Bundle-specific keys (FLAT DOT NOTATION)
personalization: Personalisation / Targeting
personalization.target-groups: Target Groups
personalization.target-group.add: Add Target Group
personalization.target-group.update.success: Target group updated successfully
personalization.target-group.update.error: Failed to update target group
personalization.target-group.delete.success: Target group deleted successfully
personalization.target-group.validation.message: Only alphanumeric characters, hyphens and underscores allowed
personalization.target-group.configuration.name: Name
personalization.target-group.configuration.description: Description
personalization.target-group.configuration.threshold: Threshold
personalization.target-group.configuration.active: Active
personalization.target-group.general-settings: General Settings
# Targeting rules
personalization.targeting-rules.navigation.title: Global Targeting Rules
studio-ui-bundle/translations/studio.{locale}.yaml
Example: studio-ui-bundle/translations/studio.en.yaml
your-bundle/translations/studio.{locale}.yaml
Example: personalization-bundle/translations/studio.en.yaml
By default, ONLY work with English translations (studio.en.yaml)!
studio.{locale}.yaml (e.g., studio.de.yaml, studio.fr.yaml)Translation files use YAML format with flat dot notation:
# ✅ CORRECT - Flat dot notation
save: Save
delete: Delete
personalization.target-groups: Target Groups
personalization.target-group.add: Add Target Group
personalization.target-group.update.success: Target group updated successfully
personalization.target-group.update.error: Failed to update target group
personalization.target-group.configuration.name: Name
personalization.target-group.configuration.description: Description
# With interpolation placeholders
element.tree.copy-success-description: '{{elementType}} "{{name}}" copied to clipboard'
# Pluralization (i18next v23 uses _one/_other suffixes)
notification.items-selected_one: '{{count}} item selected'
notification.items-selected_other: '{{count}} items selected'
# Multi-line text (use pipe for multi-line)
help.description: |
This is a longer description
that spans multiple lines.
# ❌ WRONG - Never use nested structure!
# personalization:
# target-groups: Target Groups
// Translation key:
// element.tree.copy-success-description: '{{elementType}} "{{name}}" copied to clipboard'
const { t } = useTranslation()
const message = t('element.tree.copy-success-description', {
elementType: 'Document',
name: 'Homepage'
})
// Result: 'Document "Homepage" copied to clipboard'
// File: element/actions/copy-paste/tree-copy-paste-context.tsx
const messageApi = useMessage()
const { t } = useTranslation()
const copyNode = useCallback((node: TreeNodeProps, elementType: ElementType): void => {
void messageApi.success(t('element.tree.copy-success-description', {
elementType: t(elementType), // Translate element type
name: getNodeName(node),
interpolation: { escapeValue: false } // Don't escape HTML
}))
}, [messageApi, t])
// When translation contains HTML, disable escaping
const { t } = useTranslation()
const description = t('my-feature.description', {
link: '<a href="#">Click here</a>',
interpolation: { escapeValue: false }
})
# Singular form (i18next v23 uses _one/_other suffixes)
item-selected_one: '{{count}} item selected'
# Plural form (append _other, NOT _plural)
item-selected_other: '{{count}} items selected'
const { t } = useTranslation()
const selectedCount = 5
const message = t('item-selected', { count: selectedCount })
// count = 1: "1 item selected"
// count > 1: "5 items selected"
// Pagination total display
<Pagination
showTotal={(total) => t('pagination.show-total', { total })}
/>
// Translation keys:
// pagination.show-total_one: '{{total}} item'
// pagination.show-total_other: '{{total}} items'
import { Form, Input } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const MyForm = () => {
const { t } = useTranslation()
return (
<Form>
<Form.Item
label={t('form.label.name')}
name="name"
rules={[
{ required: true, message: t('form.validation.required') }
]}
>
<Input placeholder={t('form.placeholder.enter-name')} />
</Form.Item>
</Form>
)
}
import { Button } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const Toolbar = () => {
const { t } = useTranslation()
return (
<>
<Button onClick={handleCancel}>{t('cancel')}</Button>
<Button type="primary" onClick={handleSave}>{t('save')}</Button>
</>
)
}
import { useFormModal } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const useAddItem = () => {
const modal = useFormModal()
const { t } = useTranslation()
const handleAdd = () => {
modal.input({
title: t('my-feature.add-item.title'),
label: t('my-feature.add-item.label'),
rule: {
required: true,
message: t('form.validation.required')
},
onOk: async (value) => {
// Handle add
}
})
}
return { handleAdd }
}
import { useFormModal } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const useDeleteItem = () => {
const modal = useFormModal()
const { t } = useTranslation()
const handleDelete = (itemName: string) => {
modal.confirm({
title: t('my-feature.delete.confirmation.title'),
content: (
<>
<span>{t('my-feature.delete.confirmation.text')}</span>
<br />
<b>{itemName}</b>
</>
),
okText: t('delete'),
cancelText: t('cancel'),
onOk: async () => {
// Handle delete
}
})
}
return { handleDelete }
}
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const useSaveItem = () => {
const messageApi = useMessage()
const { t } = useTranslation()
const handleSave = async () => {
try {
await saveItem()
void messageApi.success(t('my-feature.save.success'))
} catch (error) {
void messageApi.error(t('my-feature.save.error'))
}
}
return { handleSave }
}
import { createColumnHelper } from '@tanstack/react-table'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const useColumns = () => {
const { t } = useTranslation()
const columnHelper = createColumnHelper<DataType>()
return [
columnHelper.accessor('name', {
header: t('my-feature.columns.name')
}),
columnHelper.accessor('status', {
header: t('my-feature.columns.status')
})
]
}
// In module initialization
const mainNavRegistry = container.get<MainNavRegistry>(serviceIds.mainNavRegistry)
mainNavRegistry.registerMainNavItem({
path: 'MyGroup/MyFeature',
label: 'my-feature.navigation.title', // Translation key
order: 5,
widgetConfig: {
name: 'My Feature',
id: 'my-feature',
component: 'my-feature',
config: {
translationKey: 'my-feature.navigation.title', // Translation key
icon: {
type: 'name',
value: 'my-icon'
}
}
}
})
// File: personalization-bundle/.../target-group-detail.tsx
import { Form, Input, TextArea, InputNumber, Switch } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const TargetGroupDetail = ({ id }: { id: number }) => {
const { t } = useTranslation()
const [form] = Form.useForm()
return (
<Form form={form} layout="vertical">
<Panel title={t('personalization.target-group.general-settings')}>
<Form.Item
label={t('personalization.target-group.configuration.name')}
name="name"
>
<Input
disabled
placeholder={t('personalization.target-group.configuration.name')}
/>
</Form.Item>
<Form.Item
label={t('personalization.target-group.configuration.description')}
name="description"
>
<TextArea
placeholder={t('personalization.target-group.configuration.description')}
rows={4}
/>
</Form.Item>
<Form.Item
label={t('personalization.target-group.configuration.threshold')}
name="threshold"
>
<InputNumber />
</Form.Item>
<Form.Item
name="active"
valuePropName="checked"
>
<Switch
labelRight={t('personalization.target-group.configuration.active')}
/>
</Form.Item>
</Panel>
</Form>
)
}
NEVER hardcode user-facing text. ALWAYS use translation keys!
// ❌ DON'T - Hardcoded text (NEVER DO THIS!)
<Button>Save</Button>
<h1>Settings</h1>
<p>Are you sure you want to delete this item?</p>
// ✅ DO - Always use translation keys
const { t } = useTranslation()
<Button>{t('save')}</Button>
<h1>{t('settings.title')}</h1>
<p>{t('delete.confirmation.text')}</p>
# ❌ DON'T - Nested structure
personalization:
target-groups: Target Groups
target-group:
add: Add Target Group
# ✅ DO - Flat dot notation
personalization.target-groups: Target Groups
personalization.target-group.add: Add Target Group
# ❌ DON'T - Only German translations, no English base
# File: studio.de.yaml
mein-feature.titel: Mein Feature
# ✅ DO - Always have English in studio.en.yaml first
# File: studio.en.yaml
my-feature.title: My Feature
# File: studio.de.yaml
my-feature.title: Mein Feature
// DON'T DO THIS
const message = t('saved') + ' ' + itemName
// DO THIS
const message = t('saved-item', { name: itemName })
// Translation: 'saved-item': 'Saved {{name}} successfully'
// DON'T - Key doesn't exist in translation file
t('non-existent-key') // Shows the key itself
// DO - Always add keys to translation files first
// Then use them in code
t('my-feature.existing-key')
// DON'T - Manual plural handling
const message = count === 1 ? t('item') : t('items')
// DO - Use count parameter (i18next v23 uses _one/_other suffixes)
const message = t('item', { count })
// Translation keys:
// item_one: '{{count}} item'
// item_other: '{{count}} items'
Step 1: ALWAYS Add English First (REQUIRED)
Add translations to studio.en.yaml in your bundle using flat dot notation:
# File: your-bundle/translations/studio.en.yaml
# ✅ CORRECT - Flat dot notation
my-feature.title: My Feature
my-feature.description: This is my feature description
my-feature.add-item.title: Add Item
my-feature.add-item.label: Item Name
my-feature.save.success: Item saved successfully
my-feature.save.error: Failed to save item
# ❌ WRONG - Never use nested structure!
# my-feature:
# title: My Feature
Step 2: Use in Components (Never Hardcode!)
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
export const MyFeature = () => {
const { t } = useTranslation()
return (
<div>
{/* ✅ DO - Always use translation keys */}
<h1>{t('my-feature.title')}</h1>
<p>{t('my-feature.description')}</p>
{/* ❌ DON'T - Never hardcode */}
{/* <h1>My Feature</h1> */}
</div>
)
}
Step 3: Add Other Languages (Optional)
German: studio.de.yaml
# ✅ CORRECT - Flat dot notation, same keys as English
my-feature.title: Meine Funktion
my-feature.description: Dies ist meine Funktionsbeschreibung
my-feature.add-item.title: Element hinzufügen
my-feature.add-item.label: Elementname
# ❌ WRONG - Never use nested structure!
French: studio.fr.yaml
# ✅ CORRECT - Flat dot notation
my-feature.title: Ma Fonctionnalité
my-feature.description: Ceci est ma description de fonctionnalité
studio.en.yaml) is MANDATORY - Must contain all translation keyst('translation.key') in components