Skip to Content
App ShellInternationalization

Internationalization

The App Shell supports internationalization of its chrome (header, navigation, error pages) and of configuration properties (app name, menu labels, logo description, etc.).

Translations are loaded from a file-based layout following the locales/{lng}/{ns}.json convention, where {lng} is a BCP 47 language tag and {ns} is a translation namespace.

Namespaces

The App Shell locale file layout uses two namespaces:

NamespaceDescription
appShellTranslations for the App Shell chrome (header, vertical navigation, error pages, etc.). Shipped with the App Shell packages and can be overridden when building with the Vite plugin.
appTranslations used to internationalize App Shell configuration properties (app name, menu labels, logo description, etc.). Provided by the application via locale files or inline translations.

The appShell namespace is pre-bundled in English and loaded via HTTP for all other languages. The app namespace is always loaded via HTTP.

Locale files layout

Translation files follow this directory structure:

locales/ supported-locales.json ← optional manifest en/ app.json pt/ app.json de/ app.json

Each JSON file contains a flat or nested object with translation keys and their values:

{ "appName": "My Application", "pageOne": "Page One", "logoDesc": "Company logo" }

At runtime, these files are resolved relative to the translationsBaseUrl (which defaults to "./").

When using the App Shell Vite Plugin with type: "app", locale files are placed in the application’s public/locales/ directory. The plugin automatically merges the App Shell’s stock resource bundles (for the appShell namespace) with the application’s locale files, in both development and production. Local keys always take priority over the stock translations.

This means that applications only need to provide locale files for the app namespace — the appShell namespace is handled automatically by the plugin.

When type is "bundle", no upstream merging occurs — the appShell namespace translations are not copied into the output. The hosting App Shell (i.e., the type: "app" project that loads the bundle at runtime) is responsible for providing the appShell translations. However, the supported-locales.json manifest is still generated from the local locale directories (see below).

[!NOTE] The appShell namespace for English is pre-bundled directly in the App Shell code, so the App Shell chrome works even without a locales/ directory being served. Other languages require the locale files to be available via HTTP.

Supported locales manifest

The optional supported-locales.json file is a JSON array of BCP 47 language tags that declares which locales the application supports:

["en", "pt", "de"]

When present, translation backends (such as HttpResourcesBackend from @hitachivantara/app-shell-i18next) use this manifest to skip network requests for unsupported languages, letting i18next follow the fallbackLng chain instead. If the file is missing or malformed, all languages are allowed.

When building with the App Shell Vite Plugin, the plugin generates the final supported-locales.json manifest (always sorted alphabetically) based on the following logic:

  • If the application provides a supported-locales.json in public/locales/, it acts as a filter: only the locales listed there will be included in the output. Upstream locale directories from the app-shell-ui package that are not in this list are excluded from the build. This gives the application full control over which languages are shipped, while still inheriting the upstream translations for the selected locales.
  • If no supported-locales.json is provided, all upstream locales from app-shell-ui are included alongside any local locale directories. This is the default behavior when the application does not need to restrict the set of available languages.

In both cases, locales listed in the manifest but without a corresponding language directory (in either the app’s public/locales/ or the upstream app-shell-ui package) are warned about and ignored.

Overriding App Shell translations

If you need to customize any of the App Shell chrome translations (e.g., changing a header label), create a file at public/locales/{lng}/appShell.json (when using the Vite plugin) with just the keys you want to override. The plugin will deep-merge your overrides on top of the stock translations, with your keys taking priority.

Migrating from inline translations

Previously, translations were provided inline in the app-shell.config.ts via the translations property. This approach is still supported — inline translations are loaded immediately as preloaded resources in the app namespace.

To migrate to file-based translations:

  1. Create locale files (e.g., public/locales/en/app.json when using the Vite plugin) with your translation keys.
  2. Remove the translations property from app-shell.config.ts.

If both translations and locale files are present, inline translations are shown immediately while the HTTP backend fetches the locale files. When the HTTP response arrives, it overrides the inline translations.

Example

Before (inline):

// app-shell.config.ts export default { name: "appName", menu: [{ label: "pageOne", target: "/page1" }], translations: { en: { appName: "My App", pageOne: "Page One", }, pt: { appName: "Minha App", pageOne: "Página Um", }, }, } satisfies HvAppShellConfig;

After (file-based):

// app-shell.config.ts export default { name: "appName", menu: [{ label: "pageOne", target: "/page1" }], } satisfies HvAppShellConfig;
// locales/en/app.json (public/locales/en/app.json when using the Vite plugin) { "appName": "My App", "pageOne": "Page One", }
// locales/pt/app.json (public/locales/pt/app.json when using the Vite plugin) { "appName": "Minha App", "pageOne": "Página Um", }

Configuring the translations base URL

By default, the translations base URL is "./" — translation files are loaded relative to the application’s base URL. This works out of the box for applications compiled with the App Shell Vite Plugin.

For advanced scenarios (e.g., a backend server serving merged translations), the translationsBaseUrl configuration property can be set to point to a different URL:

// app-shell.config.ts export default { translationsBaseUrl: "https://my-server.com/api/", // ... } satisfies HvAppShellConfig;

The App Shell will then load translations from: https://my-server.com/api/locales/{lng}/{ns}.json

The URL is resolved relative to the configUrl (when the config was loaded from a URL) or the configured baseUrl.

Disabling HTTP translation loading

Set translationsBaseUrl: false to disable the HTTP backend entirely. The App Shell will rely solely on inline translations embedded in the config:

// app-shell.config.ts export default { translationsBaseUrl: false, translations: { en: { myKey: "My value" }, pt: { myKey: "Meu valor" }, }, } satisfies HvAppShellConfig;

This is useful for legacy setups where any extra network requests to /locales/ would produce 404 errors.

[!WARNING] Disabling HTTP loading affects both namespaces — appShell and app. The appShell namespace is still pre-bundled in English, so the App Shell chrome works in English without a locales/ directory. However, all other languages for appShell and all languages for app will no longer be loaded from HTTP. If you need translated chrome or translated configuration properties, you must supply them via inline translations.

Available languages

The App Shell ships with built-in appShell namespace translations for the following languages:

Language TagLanguage
enEnglish
ptPortuguese
deGerman
frFrench
jaJapanese
zh-CNChinese (Simplified)
zh-TWChinese (Traditional)

English is pre-bundled and available synchronously. Other languages are loaded via the HTTP backend.

Changing the language at runtime

The useHvAppShellI18n hook provides access to the current language and a function to change it at runtime. This is the recommended way for Views and other modules to stay in sync with the App Shell’s language, regardless of which i18n library they use internally.

See the API Reference for details.

View translations

The App Shell’s translation namespaces (appShell and app) are intended for the App Shell chrome and configuration properties only. Views must manage their own translations independently, using whichever i18n library or approach they prefer.

To keep the View’s language in sync with the App Shell, use the useHvAppShellI18n hook to read the current language and react to changes.

Views can and should use the same locales/ repository for their own translations, using different namespace names to avoid conflicts with appShell and app. For example, a View could load its translations from locales/{lng}/myView.json.

Advanced: i18next library sharing

[!CAUTION] This section covers an advanced topic relevant only when Views or Providers are compiled together with the App Shell (i.e., in the type: "app" project) and happen to use the i18next library internally.

The App Shell uses i18next and react-i18next internally. These libraries are not shared dependencies loaded externally at runtime — they are bundled into the App Shell compilation produced by the Vite plugin.

This means that Application Bundles (type: "bundle") loaded at runtime have their own copy of i18next and there is no interaction between the two.

However, Views or Providers compiled in the same type: "app" project share the i18next and react-i18next libraries at the bundler level. In this scenario:

  • The react-i18next useTranslation hook uses React context to find the nearest I18nextProvider. If a View or a system/global provider installs its own I18nextProvider higher in the component tree, it may shadow the App Shell’s provider. The App Shell handles this internally, but downstream code using useTranslation without an explicit instance may pick up the wrong provider.
  • To avoid conflicts, code compiled with the App Shell that uses i18next should create its own i18next instance and pass it explicitly to its I18nextProvider or useTranslation calls.
Last updated on