In any internationalization system, managing translation keys is a critical foundation. This article explores how we implemented a UIStrings collection in Payload CMS that bridges the gap between frontend translation needs and content management, while providing a smooth editing experience for translators and maintaining strict data integrity.
At its heart, our UIStrings collection manages key-value pairs where:
The key is a unique identifier used in the frontend (e.g., comments-textfield-label
)
The value is the translated text in various languages
Additional metadata provides context for translators
These translation keys are used in the frontend through React's internationalization utilities:
// Using react-intl directly
intl.formatMessage({ id: "comments-textfield-label" })
// Using the FormattedMessage component
<FormattedMessage id="comments-submit-button" />
Let's break down the key components of our UIStrings collection:
const UIStrings: CollectionConfig = {
slug: "ui-strings",
labels: {
singular: "UI String",
plural: "UI Strings",
},
versions: true,
// ... fields configuration
};
One of our key design decisions was to use the translation key itself as the document ID, a feature called Custom ID in Payload. This approach has several benefits:
{
name: "id",
type: "text",
validate: validateAlphaNumeric("ID"),
}
Why Custom IDs?
Eliminates redundant identification (no separate Payload ID and translation key)
Makes API responses cleaner and more intuitive
Ensures direct correlation between frontend usage and database records
Important Considerations:
To help translators work effectively, we implemented a custom "Default Text" field that shows the reference text from the default language:
{
name: "defaultText",
type: "ui",
admin: {
components: {
Field: DefaultTextField,
Cell: DefaultTextCell,
},
},
}
The DefaultTextField component provides a clear reference:
export function DefaultTextField({ label }: { label: string }) {
const { id } = useDocumentInfo();
return (
id && (
<div className="default-text-field">
<Label label={`${label} (${defaultLocale})`} />
<div>
<DefaultTextCell rowData={{ id: id.toString() }} />
</div>
</div>
)
);
}
This component fetches and displays the default language text using SWR for efficient data loading, used in the edit form as well as the list overview:
export function DefaultTextCell({ rowData }: { rowData: Pick<UiString, "id"> }) {
const { data, error } = useSWR(
`/api/${slug}/${rowData.id}?locale=${defaultLocale}`,
fetcher
);
if (error) return <span>Error loading text</span>;
if (!data) return <span>Loading...</span>;
return <span>{data.text || "<No Default Text>"}</span>;
}
The main translation field is implemented with localization support:
{
name: "text",
type: "text",
label: "Text",
localized: true,
admin: {
components: {
Field: (props) => (
<InputField {...props} minVariations={3} maxVariations={5} />
),
},
},
}
Notable features:
Localized field enabling translations for all supported languages
Custom InputField component supporting AI-powered translation suggestions
Configurable minimum and maximum variation counts for suggestions
Our access control implementation follows clear security principles and uses the rbacHas function which was discussed in the RBAC (Role Based Access Control) article:
access: {
read: () => true, // Public access for frontend use
create: rbacHas(ROLE_ADMIN), // Only admins can create new keys
update: rbacHas([ROLE_TRANSLATOR, ROLE_EDITOR]), // Translators can modify text
delete: rbacHas(ROLE_ADMIN), // Only admins can remove keys
readVersions: () => true,
}
This pattern:
Restricts structural changes (create/delete) to administrators
Allows translators and editors to update translations
Keeps translations and version history publicly accessible
To maintain clean data and ensure proper filtering in the admin UI, we implemented a beforeChange hook:
hooks: {
beforeChange: [
({ data }) => {
// Remove empty text fields for proper admin UI filtering
if (data.text === "") {
delete data.text;
}
return data;
},
],
}
This is important when leveraging the filtering of Payload CMS. This way in the list vew under "Filters" you can select "Text" -> "exists" -> "false" and will be shown all keys which do not have a text set for the current locale.
Our implementation includes comprehensive version control through Payload's versioning system:
const UIStrings: CollectionConfig = {
// ... other configuration
versions: true,
};
The versioning system provides several key benefits for translation management:
Change History
Every update to a translation is automatically versioned
Editors can view who made each change and when
Complete history of translations in all languages is preserved
Rollback Capabilities
Editors can restore previous versions if needed
Useful for correcting mistakes or reverting controversial changes
Maintains all metadata when rolling back
Audit Trail
Track when and why translations were modified
Identify patterns in translation updates
Support quality control processes
Payload CMS provides a built-in version interface that shows:
A timeline of all changes
The user who made each change
Timestamps for all modifications
Diff views comparing versions
One-click restore functionality
This interface is particularly valuable for:
The REST API provides clean, focused responses:
{
"id": "comments-textfield-label",
"createdAt": "2024-09-12T07:35:42.638Z",
"updatedAt": "2024-09-12T07:35:42.638Z",
"text": "Kommentar",
"drafts": []
}
The default text reference is styled for clarity:
.default-text-field {
margin-bottom: var(--spacing-field);
& > div {
background-color: #eee;
padding: calc(var(--base) / 4);
padding-left: calc(var(--base) / 2);
}
}
This styling:
Clearly separates the reference text visually
Maintains consistent spacing with other fields
Uses subdued colors to avoid confusion with editable fields
Our UIStrings collection implementation provides a robust foundation for translation management while addressing several key challenges:
Efficient key-value storage and retrieval
Clean API responses
Strong access control
Enhanced translator experience
Data integrity protection
The system balances flexibility with necessary constraints, ensuring that translation keys remain stable while allowing efficient translation workflows. Future articles will explore additional features like AI-powered translation suggestions and advanced collaboration tools.
Pas encore de commentaires, soyez le premier :
© 2024 par Moritz Thomas