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:
| Namespace | Description |
|---|---|
appShell | Translations 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. |
app | Translations 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.jsonEach 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
appShellnamespace for English is pre-bundled directly in the App Shell code, so the App Shell chrome works even without alocales/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.jsoninpublic/locales/, it acts as a filter: only the locales listed there will be included in the output. Upstream locale directories from theapp-shell-uipackage 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.jsonis provided, all upstream locales fromapp-shell-uiare 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:
- Create locale files (e.g.,
public/locales/en/app.jsonwhen using the Vite plugin) with your translation keys. - Remove the
translationsproperty fromapp-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 —
appShellandapp. TheappShellnamespace is still pre-bundled in English, so the App Shell chrome works in English without alocales/directory. However, all other languages forappShelland all languages forappwill no longer be loaded from HTTP. If you need translated chrome or translated configuration properties, you must supply them via inlinetranslations.
Available languages
The App Shell ships with built-in appShell namespace translations for the following languages:
| Language Tag | Language |
|---|---|
en | English |
pt | Portuguese |
de | German |
fr | French |
ja | Japanese |
zh-CN | Chinese (Simplified) |
zh-TW | Chinese (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-i18nextuseTranslationhook uses React context to find the nearestI18nextProvider. If a View or a system/global provider installs its ownI18nextProviderhigher in the component tree, it may shadow the App Shell’s provider. The App Shell handles this internally, but downstream code usinguseTranslationwithout 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
I18nextProvideroruseTranslationcalls.