Locale resolution

SitecoreAI doesn't detect a site visitor's locale. Instead, every layout, item, or search query takes an explicit language argument, and your app is responsible for deciding what value to pass. If you specify a language, the response only includes content in that language or a fallback language.

query {
  layout(site: "my-site" language: "fr-FR" routePath: "/") {
    item {
      rendered
    }
  }
}

Determining the requested locale

Your app decides how a visitor's locale maps to a request, typically using one (or a combination) of these strategies:

  • A locale segment in the URL - such as /fr-fr/products or /en/products. This is the most common approach for statically generated or SEO-friendly sites because the locale is visible in the URL and can be crawled, bookmarked, and shared.
  • A dedicated hostname per locale - such as fr.example.com versus example.com. This works well when each locale is a separate site definition (see multi-site resolution), but requires DNS and hosting configuration per locale.
  • An Accept-Language request header - used to pick a default locale on first visit (for example, to redirect / to /fr-fr/).
  • A cookie or session value - to persist a locale a visitor has explicitly chosen.

Regardless of the strategy you use, resolve it to one of the locales configured for your site before making your Sitecore query, and fall back to the site's default language when nothing matches.

function resolveLocale(request, configuredLocales, defaultLocale):
    localeFromPath = extractLocaleSegment(request.path, configuredLocales)
    if localeFromPath:
        return localeFromPath
    return matchAcceptLanguage(request.headers, configuredLocales) or defaultLocale

Determining available locales

SitecoreAI doesn't expose a single GraphQL field that lists all available locales for an arbitrary item. In practice, you determine available locales in one of the following ways:

  • Hardcode your app's supported locale list to match your Sitecore languages. Maintain a list of locales in your app (matching the languages enabled for your site in SitecoreAI's language settings), and use it to validate and parse locale segments in incoming request paths. This is a hardcoded approach: whenever a language is added or removed in SitecoreAI, you must also update your app's list, or your app won't recognize the change.

  • Query a site's default language. Use site.siteInfo(site: "<name>") { language } (or siteInfoCollection, described in multi-site resolution) to retrieve the default language configured for a site. If your environment models each language as its own site definition sharing a root path (a supported pattern when language-based site resolution is enabled), querying siteInfoCollection also gives you the set of languages available for that logical site, one result per language and site pair.

  • Query per-item language availability. If you need to know which locales a specific item has versions in (for example, to build a language switcher that only shows a page's translated versions instead of every site locale), query the item's languages field:

    query LanguageAvailabilityQuery($itemPath: String!, $language: String!) {
      item(path: $itemPath language: $language) {
        languages {
          language {
            name
            nativeName
          }
        }
      }
    }

Language fallback

Rather than requiring your app to merge languages together, SitecoreAI can automatically substitute fallback language content directly in the query response when content doesn't exist in the requested language. This is called language fallback, and it's disabled by default.

Fallback must be enabled at the environment level for it to affect Delivery/Preview GraphQL responses - site-level fallback settings only affect the Page builder authoring experience. Fallback can be configured with a chain of languages (for example, en-NZ → en-AU → en), and can apply at the item level (the whole item falls back to the next language in the chain) or the field level (individual empty fields fall back while fields with a value in the requested language are unaffected, so a single component can mix languages field by field). For how to configure a fallback chain, see Language fallback.

Important

Fallback language versions must be published to Experience Edge at least once for the fallback dependency to be established. After that, publishing the primary language automatically keeps the fallback dependency up to date.

Handling fallback in your app

Because fallback is calculated by SitecoreAI and already included in the rendered content response, your app doesn't need to implement any language-merging logic. What your app does need to handle:

  • If fallback is not configured (or a fallback chain is exhausted) and a translation doesn't exist, the query returns null for the missing item or field. We recommend omitting the field or component in that case rather than rendering placeholder text such as "not translated", or falling back to the site's default language at the user interface level.
  • The rendered layout JSON doesn't indicate which parts of the response came from a fallback language as opposed to the requested language. If your app needs to distinguish this (for example, to show a "This page isn't available in your language" banner), you need to query for it explicitly. Check whether a version exists in the requested language before relying on fallback.

Site dictionary

Apart from page content, your site also has translatable UI strings, such as button labels, form placeholders, and error messages, managed by content authors independently of page content. These are stored in a flat key-value site dictionary separate from component fields. Site dictionaries aren't included in the layout response for normal page rendering.

Query the dictionary for a site and language with the site.siteInfo field:

query DictionaryQuery($siteName: String!, $language: String!, $pageSize: Int!, $after: String) {
  site {
    siteInfo(site: $siteName) {
      dictionary(language: $language, first: $pageSize, after: $after) {
        results {
          key
          value
        }
        pageInfo {
          endCursor
          hasNext
        }
      }
    }
  }
}
{
  "results": [
    { "key": "Search-Input-Placeholder", "value": "Search..." },
    { "key": "Subscribe-Button-Label", "value": "Subscribe" }
  ]
}

The response is paginated. Use pageInfo.endCursor and pageInfo.hasNext to fetch subsequent pages until you collect every entry. Because the dictionary changes infrequently and is language-specific, fetch and cache it once per language rather than per request. You can then look up individual keys from that cache when rendering components.

Enumerating pages for static generation

If your framework supports static site generation (SSG), it needs a complete list of a site's pages to pre-render at build time. Retrieve it with the site.siteInfo field's routes query, once per site and language:

query RoutesQuery($siteName: String!, $language: String!, $pageSize: Int!, $after: String) {
  site {
    siteInfo(site: $siteName) {
      routes(language: $language, first: $pageSize, after: $after) {
        results {
          routePath
        }
        total
        pageInfo {
          endCursor
          hasNext
        }
      }
    }
  }
}

Results are paginated. Request a larger page size (for example, 100) to reduce the number of calls needed for a large site. For a multilingual site, call this once per configured locale to collect the full set of paths to pre-render.

Next steps

Continue to redirects to learn how content-managed redirect rules are structured and evaluated.

If you have suggestions for improving this article, let us know!