Localized content with AEM Headless

AEM provides a Translation Integration Framework for headless content, allowing Content Fragments and supporting assets to be easily translated for use across locales. This is the same framework used to translate other AEM content, such as Pages, Experience Fragments, Assets, and Forms. Once headless content has been translated, and published, it is ready for consumption by headless applications.

Assets folder structure

Ensure that the localized Content Fragments in AEM follow the recommended localization structure.

Localized AEM assets folders

The locale folders must be siblings, and the folder name, rather than title, must be a valid ISO 639-1 code representing the locale of the content contained in the folder.

The locale code is also the value used to filter the Content Fragments returned by the GraphQL query.

Locale code AEM path Content locale
de /content/dam/…/de/… German content
en /content/dam/…/en/… English content
es /content/dam/…/es/… Spanish content

GraphQL query

AEM provides a _locale GraphQL filter that automatically filters content by locale code . For example, querying all English adventures in the WKND reference demo project would look like:

{
  adventureList(_locale: "en") {
    items {
      _path
      adventureTitle
    }
  }
}

The _locale filter requires the use of AEM’s asset folder-base localization convention.

React example

Let’s create a simple React application that controls what Adventure content to query from AEM based on a locale selector using the _locale filter.

When English is selected in the locale selector, then English Adventure Content Fragments under /content/dam/wknd/en are returned, when Spanish is selected, then Spanish Content Fragments under /content/dam/wknd/es, and so on, and so forth.

Localized React example app

Create a LocaleContext

First, create a React context to allow the locale to be used across the React application’s components.

// src/LocaleContext.js

import React from 'react'

const DEFAULT_LOCALE = 'en';

const LocaleContext = React.createContext({
    locale: DEFAULT_LOCALE,
    setLocale: () => {}
});

export default LocaleContext;

Create a LocaleSwitcher React component

Next, create a locale switcher React component that set’s the LocaleContext’s value to the user’s selection.

This locale value is used to drive the GraphQL queries, ensuring they only return content matching the selected locale.

// src/LocaleSwitcher.js

import { useContext } from "react";
import LocaleContext from "./LocaleContext";

export default function LocaleSwitcher() {
  const { locale, setLocale } = useContext(LocaleContext);

  return (
    <select value={locale}
            onChange={e => setLocale(e.target.value)}>
      <option value="de">Deutsch</option>
      <option value="en">English</option>
      <option value="es">Español</option>
    </select>
  );
}

Query content using the _locale filter

The Adventures component queries AEM for all adventures by locale and lists their titles. This is achieved by passing the locale value stored in the React context, to the query using the _locale filter.

This approach can be extended to other queries in your application, ensuring all queries only include content specified by a user’s locale selection.

The querying against AEM is performed in the custom React hook useGraphQL, described in more detail on the Querying AEM GraphQL documentation.

// src/Adventures.js

import { useContext } from "react"
import { useGraphQL } from './useGraphQL'
import LocaleContext from './LocaleContext'

export default function Adventures() {
    const { locale } = useContext(LocaleContext);

    let {data} = useGraphQL(`{
            adventureList(_locale: "${locale}") {
                items {
                _path
                adventureTitle
             }
        }
    }`);

    return (
        <ul>
            {data?.adventureList?.items?.map((adventure, index) => {
                return <li key={index}>{adventure.adventureTitle}</li>
            })}
        </ul>
    )
}

Define the App.js

Lastly, tie it all together by wrapping the React application in with the LanguageContext.Provider and setting the locale value. This allows the other React components, LocaleSwitcher, and Adventures to share the locale selection state.

// src/App.js

import { useState, useContext } from "react";
import LocaleContext from "./LocaleContext";
import LocaleSwitcher from "./LocaleSwitcher";
import Adventures from "./Adventures";

export default function App() {
  const [locale, setLocale] = useState(useContext(LocaleContext).locale);

  return (
    <LocaleContext.Provider value={{locale, setLocale}}>
      <LocaleSwitcher />
      <Adventures />
    </LocaleContext.Provider>
  );
}

On this page