Experience Sitecore ! | Dictionary in XM Cloud

Experience Sitecore !

More than 300 articles about the best DXP by Martin Miles

Dictionary in XM Cloud

Localization is more than translation - it's about delivering a seamless, culturally relevant experience for every visitor. In Sitecore XM Cloud, dictionary items provide a simple, robust way to centralize UI text and labels for all languages, directly accessible in your Next.js frontend. Below, I’ll walk through the practical setup, usage patterns, and expert recommendations based on Sitecore’s official documentation and proven community practices.

What Are Dictionary Items?

Dictionary items in XM Cloud let you store and manage all your UI phrases centrally.
Instead of hardcoding "Read More", "Submit",  or error messages, you create dictionary keys under your site’s /Dictionary item. Each key holds localized values for every language you support.

Why:
  • Centralized: Update once, used everywhere
  • Multi-language: Add a new language, all keys covered
  • Consistent: No more stray hardcoded strings

How Dictionary Items Work in XM Cloud

In XM Cloud, dictionary items are stored at: /sitecore/content/{collection}/{site}/Dictionary

Each dictionary item represents a key (e.g., "read"), and under it, each language version holds the translated phrase.

For multi-site or SXA setups, you can create a shared dictionary (often in a Shared site or at the tenant root), and then set up a fallback so multiple sites reuse the same dictionary keys. On the integration side, XM Cloud provides a Dictionary Service API, available via REST or GraphQL, which populates the dictionary data at build or runtime for your Next.js app.

The Single useTranslation Hook Pattern (with Fallback Defaults)

To keep things tidy, many teams prefer a single useTranslations hook. Here’s a pattern that ensures you never show a blank label, even if the dictionary entry is missing, by supplying a fallback default:

import { useI18n } from 'next-localization';

export function useTranslation() {
  const i18n = useI18n();
  // Utility: always show a fallback if dictionary value is missing
  const getTranslation = (key, fallback) => i18n?.t(key) || fallback;

  return {
    readLabel: getTranslation('read', 'Read'),
    submitLabel: getTranslation('submit', 'Submit'),
    // Add more keys as needed
  };
}

// Usage in a component
const translations = useTranslation();

return (
  <div>
    <button>{translations.readLabel}</button>
  </div>
);

Why this is better:

  • No undefined or blank UI if a dictionary key is missing
  • All translations accessible via one hook - simple, scalable
  • Easy to add more keys as your UI grows

Using Shared SXA Dictionaries & Fallback Domains

If you’re using SXA or multiple sites, you can set up a shared dictionary and configure fallback so every site can leverage the same keys.

Setup Steps:

  1. Create a Shared Dictionary. Place it in a shared location, such as /sitecore/content/{collection}/Shared/Dictionary
  2. Set Dictionary Domain. In Sitecore, under your site’s Dictionary item, set the Dictionary Domain field to the shared dictionary’s path. (Official Doc)
  3. Configure Fallback. On your site’s dictionary, enable "fallback domain" to point to the shared dictionary.

In your Next.js app, the Dictionary Service will automatically merge keys from both the site and shared domains. 

Storybook: Mocking Dictionaries for Isolated UI Development

When using Storybook, components expecting dictionary values may break outside the app context. To avoid this, you can mock the next-localization context in .storybook/preview.tsx:

import { I18nProvider } from 'next-localization';

const mockDictionary = {
  read: 'Read',
  submit: 'Submit',
  // Add other keys as needed
};

export const decorators = [
  (Story) => (
    <I18nProvider lngDict={mockDictionary} locale="en">
      <Story />
    </I18nProvider>
  ),
];
Why this matters:
  • Ensures all components render correctly in isolation
  • Lets designers and QA see accurate UI text in stories
  • Helps catch missing dictionary keys before production

Bonus: Dictionary Migration and Conversion SPE Script

The below script copies dictionary items from an old XP database (referred to as old) to the master database of the locally-run XM Cloud environment and also converts Dictionary entries from a custom format of the Habitat website (which many folks have used as a starter kit, regardless) to the XM Cloud format which is hardly-definied by a dictionary temapltes coming from items resource file:
param(
    [string]$SourceDatabase = "old",
    [string]$SourcePath = "/sitecore/content/Habitat/Global/Dictionary",
    [string]$TargetDatabase = "master",
    [string]$TargetPath = "/sitecore/content/Zont/Habitat/Dictionary"
)

$srcFolderTemplate = "{98E4BDC6-9B43-4EB2-BAA3-D4303C35852E}"
$srcEntryTemplate = "{EC4DD3F2-590D-404B-9189-2A12679749CC}"
$srcPhraseField = "{DDACDD55-5B08-405F-9E58-04F09AED640A}"

$trgFolderTemplate = "{267D9AC7-5D85-4E9D-AF89-99AB296CC218}"
$trgEntryTemplate = "{6D1CD897-1936-4A3A-A511-289A94C2A7B1}"
$trgKeyField = "{580C75A8-C01A-4580-83CB-987776CEB3AF}"
$trgPhraseField = "{2BA3454A-9A9C-4CDF-A9F8-107FD484EB6E}"

function Copy-DictionaryItemsRecursive {
    param(
        [Sitecore.Data.Items.Item]$SrcParent,
        [Sitecore.Data.Items.Item]$TrgParent,
        [Sitecore.Data.Database]$SrcDB,
        [Sitecore.Data.Database]$TrgDB,
        [int]$Depth = 0
    )

    $indent = (" " * ($Depth * 2))
    $children = $SrcParent.GetChildren()
    Write-Output "$indent- Processing source: $($SrcParent.Paths.FullPath) | Child count: $($children.Count)"

    foreach ($child in $children) {
        Write-Output "$indent  - Processing child: $($child.Paths.FullPath) | TemplateID: $($child.TemplateID) | Name: $($child.Name)"
        $templateId = $child.TemplateID.ToString()
        $newItemName = $child.Name
        $newItem = $null

        try {
            # Check if this item already exists by name under TrgParent
            $existingByName = $TrgParent.Children | Where-Object { $_.Name -eq $newItemName }
            if ($existingByName) {
                Write-Output "$indent    - Using existing target item (by name): $($existingByName.Paths.FullPath)"
                $newItem = $existingByName
            } else {
                # Prepare template item
                $templateItem = $null
                if ($templateId -eq $srcFolderTemplate) {
                    $templateItem = $TrgDB.GetTemplate($trgFolderTemplate)
                    Write-Output "$indent    - Using target template (folder): $($templateItem -ne $null)"
                } elseif ($templateId -eq $srcEntryTemplate) {
                    $templateItem = $TrgDB.GetTemplate($trgEntryTemplate)
                    Write-Output "$indent    - Using target template (entry): $($templateItem -ne $null)"
                }
                if (-not $templateItem) { throw "$indent    - ERROR: Target template not found for $newItemName" }

                # Attempt to create under the current TrgParent
                Write-Output "$indent    - Attempting to Add '$newItemName' under $($TrgParent.Paths.FullPath)"
                $newItem = $TrgParent.Add($newItemName, $templateItem)
                if (-not $newItem) { throw "$indent    - ERROR: Failed to add item $newItemName under $($TrgParent.Paths.FullPath)" }
                Write-Output "$indent    - Created item: $($newItem.Paths.FullPath) [ID: $($newItem.ID)]"
            }

            # Recurse or fill entry fields
            if ($templateId -eq $srcFolderTemplate) {
                Write-Output "$indent    - Recursing into folder: $($child.Paths.FullPath)"
                Copy-DictionaryItemsRecursive -SrcParent $child -TrgParent $newItem -SrcDB $SrcDB -TrgDB $TrgDB -Depth ($Depth + 1)
            } elseif ($templateId -eq $srcEntryTemplate) {
                foreach ($lang in $child.Languages) {
                    $srcItemLang = $SrcDB.GetItem($child.ID, $lang)
                    if (-not $srcItemLang) { throw "$indent    - ERROR: Source item language $lang missing for $($child.Paths.FullPath)" }
                    $trgItemLang = $TrgDB.GetItem($newItem.ID, $lang)
                    if (-not $trgItemLang) {
                        $trgItemLang = $newItem.Versions.AddVersion($lang)
                        if (-not $trgItemLang) { throw "$indent    - ERROR: Failed to add language version $lang to $($newItem.Paths.FullPath)" }
                    }
                    $phraseValue = $srcItemLang.Fields[$srcPhraseField].Value
                    $trgItemLang.Editing.BeginEdit()
                    $trgItemLang.Fields[$trgKeyField].Value = $newItemName
                    $trgItemLang.Fields[$trgPhraseField].Value = $phraseValue
                    $trgItemLang.Editing.EndEdit()
                    Write-Output "$indent    - Populated entry ($lang): $($newItem.Paths.FullPath) [ID: $($newItem.ID)]"
                }
            } else {
                Write-Output "$indent    - Skipping unsupported template: $($child.Name) [$templateId]"
            }
        } catch {
            Write-Output "$indent    - ERROR: $($_.Exception.Message) at item $($child.Paths.FullPath)"
            throw
        }
    }
}

try {
    $srcDB = Get-Database $SourceDatabase
    $trgDB = Get-Database $TargetDatabase
    $srcParent = $srcDB.GetItem($SourcePath)
    $trgParent = $trgDB.GetItem($TargetPath)
    Write-Output "Start: Copying dictionary items from $SourcePath [$SourceDatabase] to $TargetPath [$TargetDatabase] ..."
    if (-not $srcParent) { throw "Source parent item not found: $SourcePath in DB: $SourceDatabase" }
    if (-not $trgParent) { throw "Target parent item not found: $TargetPath in DB: $TargetDatabase" }

    Copy-DictionaryItemsRecursive -SrcParent $srcParent -TrgParent $trgParent -SrcDB $srcDB -TrgDB $trgDB -Depth 0
    Write-Output "Dictionary copy completed successfully."
} catch {
    Write-Output "SCRIPT ERROR: $($_.Exception.Message)"
    exit 1
}

Summary & Best Practices

  • Use a single, type-safe useTranslation hook for clarity and safety
  • Always supply fallback defaults to prevent UI errors
  • Prefer shared dictionaries for multi-site projects? Configure fallback domains in Sitecore for reusability
  • Mock dictionary data in Storybook to maintain robust component previews

Hope you find this post helpful!

Loading