docs: add changeset and documentation for translations CLI commands

Adds a changeset for the new translations export/import CLI commands.
Updates the internationalization docs with the CLI-based workflow as
the recommended approach for managing translations, and adds the
commands to the CLI reference. Also adds the internationalization
page to the mkdocs navigation.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-02-18 00:41:23 +01:00
parent 356cff278f
commit fd50cb3401
4 changed files with 240 additions and 16 deletions
@@ -0,0 +1,9 @@
---
'@backstage/cli': minor
---
Added `translations export` and `translations import` commands for managing translation files.
The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app.
Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping.
+164 -16
View File
@@ -1,12 +1,12 @@
---
id: internationalization
title: Internationalization (Experimental)
description: Documentation on adding internationalization to the plugin
title: Internationalization
description: Documentation on adding internationalization to plugins and apps
---
## Overview
The Backstage core function provides internationalization for plugins. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
The Backstage core function provides internationalization for plugins and apps. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
## For a plugin developer
@@ -183,16 +183,56 @@ return (
The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message.
## For an application developer overwrite plugin messages
## For an application developer
Step 1: Create translation resources
As an app developer you can both override the default English messages of any plugin, and provide translations for additional languages.
You should separate different translations to their own files and import them in the main file:
### Overriding messages
To customize specific messages without adding new languages, create a translation resource that overrides the default English messages:
```ts
// packages/app/src/translations/catalog.ts
import { createTranslationResource } from '@backstage/frontend-plugin-api';
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
export const catalogTranslations = createTranslationResource({
ref: catalogTranslationRef,
translations: {
en: () =>
Promise.resolve({
default: {
'indexPage.title': 'Service directory',
'indexPage.createButtonTitle': 'Register new service',
},
}),
},
});
```
Then register it in your app:
```diff
+ import { catalogTranslations } from './translations/catalog';
const app = createApp({
+ __experimentalTranslations: {
+ resources: [catalogTranslations],
+ },
})
```
You only need to include the keys you want to override — any missing keys fall back to the plugin's defaults.
### Adding language translations
To add support for additional languages, create translation resources with lazy-loaded message files for each language:
```ts
// packages/app/src/translations/userSettings.ts
import { createTranslationResource } from '@backstage/core-plugin-api/alpha';
import { createTranslationResource } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
export const userSettingsTranslations = createTranslationResource({
@@ -203,10 +243,12 @@ export const userSettingsTranslations = createTranslationResource({
});
```
The translation messages can be defined using `createTranslationMessages` for type safety:
```ts
// packages/app/src/translations/userSettings-zh.ts
import { createTranslationMessages } from '@backstage/core-plugin-api/alpha';
import { createTranslationMessages } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
const zh = createTranslationMessages({
@@ -221,7 +263,7 @@ const zh = createTranslationMessages({
export default zh;
```
It's also possible to export the list of messages directly:
Or as a plain object export:
```ts
// packages/app/src/translations/userSettings-zh.ts
@@ -239,11 +281,7 @@ export default {
};
```
You should change `zh` under the translations object to your local language.
Step 2: Config translations in `packages/app/src/App.tsx`
In an app you can both override the default messages, as well as register translations for additional languages:
Register it with the available languages declared:
```diff
+ import { userSettingsTranslations } from './translations/userSettings';
@@ -256,6 +294,116 @@ In an app you can both override the default messages, as well as register transl
})
```
Step 3: Check everything is working correctly
Go to the Settings page — you should see language switching buttons. Switch languages to verify your translations are loaded correctly.
Go to `Settings` page, you should see change language buttons just under change theme buttons. And then switch language, you should see language had changed
### Using the CLI for full translation workflows
When translating your app to other languages at scale — especially when working with translation management systems (TMS) like Smartling, Crowdin, or Lokalise — the Backstage CLI provides `translations export` and `translations import` commands that automate the extraction and wiring of translation messages across all your plugin dependencies.
#### Exporting default messages
From your app package directory (e.g. `packages/app`), run:
```bash
yarn backstage-cli translations export
```
This scans all frontend plugin dependencies (including transitive ones) for `TranslationRef` definitions and writes their default English messages as JSON files:
```text
translations/
manifest.json
messages/
catalog.en.json
org.en.json
scaffolder.en.json
...
```
Each `.en.json` file contains the flattened message keys and their default values:
```json
{
"indexPage.title": "All your components",
"indexPage.createButtonTitle": "Create new component",
"entityPage.notFound": "Entity not found"
}
```
#### Creating translations
Copy the exported files and translate them for your target languages:
```bash
cp translations/messages/catalog.en.json translations/messages/catalog.zh.json
```
Then edit `catalog.zh.json` with the translated strings. You only need to include the keys you want to translate — missing keys fall back to the English defaults at runtime.
#### Generating wiring code
Once you have translated files in place, run:
```bash
yarn backstage-cli translations import
```
This generates a TypeScript module at `src/translations/resources.ts` that wires everything together:
```ts
// This file is auto-generated by backstage-cli translations import
// Do not edit manually.
import { createTranslationResource } from '@backstage/frontend-plugin-api';
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
export default [
createTranslationResource({
ref: catalogTranslationRef,
translations: {
zh: () => import('../../translations/messages/catalog.zh.json'),
},
}),
];
```
Import the generated resources in your app:
```ts
import translationResources from './translations/resources';
const app = createApp({
__experimentalTranslations: {
availableLanguages: ['en', 'zh'],
resources: translationResources,
},
});
```
#### Custom file patterns
By default, message files use the pattern `{id}.{lang}.json` (e.g. `catalog.en.json`). You can change this with the `--pattern` option:
```bash
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
```
This produces a directory structure grouped by language instead:
```text
translations/messages/en/catalog.json
translations/messages/zh/catalog.json
```
The pattern is stored in the manifest, so the `import` command automatically uses the same layout.
#### Integration with a TMS
The exported JSON files are standard key-value pairs compatible with most translation management systems. A typical workflow looks like:
1. Run `translations export` to generate the source English files
2. Upload the `.en.json` files to your TMS
3. Download the translated files from your TMS into the `messages/` directory
4. Run `translations import` to regenerate the wiring code
For full command reference, see the [CLI commands documentation](../tooling/cli/03-commands.md#translations-export).
+66
View File
@@ -24,6 +24,7 @@ repo [command] Command that run across an entire
package [command] Lifecycle scripts for individual packages
migrate [command] Migration utilities
versions:bump [options] Bump Backstage packages to the latest versions
translations [command] Translation message management
clean Delete cache directories [DEPRECATED]
build-workspace <workspace-dir> [packages...] Builds a temporary dist workspace from the provided
packages
@@ -428,6 +429,71 @@ YAML file that can be referenced in the GitHub integration configuration.
Usage: backstage-cli create-github-app <github-org>
```
## translations export
Export translation messages from all frontend plugin dependencies of the current
package. This command must be run from within a package directory (e.g.
`packages/app`), not from the repository root.
The command discovers all `TranslationRef` definitions in the dependency tree,
extracts their default messages using the TypeScript type system, and writes
them as JSON files along with a manifest.
For more details on the translation workflow, see the
[Internationalization](../../plugins/internationalization.md) documentation.
```text
Usage: backstage-cli translations export [options]
Options:
--output <dir> Output directory for exported messages and manifest (default: "translations")
--pattern <pattern> File path pattern for message files, with {id} and {lang}
placeholders (default: "{id}.{lang}.json")
-h, --help display help for command
```
### Examples
Export translations with default settings:
```bash
cd packages/app
yarn backstage-cli translations export
```
Export with language-based directory grouping:
```bash
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
```
## translations import
Generate translation resource wiring code from translated JSON files. Reads the
manifest and translated message files produced by `translations export`, and
generates a TypeScript module that creates `TranslationResource` objects for each
translated ref.
```text
Usage: backstage-cli translations import [options]
Options:
--input <dir> Input directory containing the manifest and translated message files (default: "translations")
--output <path> Output path for the generated wiring module (default: "src/translations/resources.ts")
--pattern <pattern> File path pattern for message files, with {id} and {lang}
placeholders (default: "{id}.{lang}.json")
-h, --help display help for command
```
### Examples
Generate wiring code with default settings:
```bash
cd packages/app
yarn backstage-cli translations import
```
## info
Outputs debug information which is useful when opening an issue. Outputs system
+1
View File
@@ -142,6 +142,7 @@ nav:
- Composability System: 'plugins/composability.md'
- Plugin Analytics: 'plugins/analytics.md'
- Feature Flags: 'plugins/feature-flags.md'
- Internationalization (i18n): 'plugins/internationalization.md'
- OpenAPI:
- Schema-first plugins with OpenAPI (Experimental): 'openapi/01-getting-started.md'
- Generate a client from your OpenAPI spec: 'openapi/generate-client.md'