+);
+```
+
+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
+
+As an app developer you can both override the default English messages of any plugin, and provide translations for additional languages.
+
+### Overriding messages
+
+To customize specific messages without adding new languages, create a translation extension using `TranslationBlueprint` from `@backstage/plugin-app-react` together with `createTranslationMessages` from `@backstage/frontend-plugin-api`:
+
+```ts
+import { createTranslationMessages } from '@backstage/frontend-plugin-api';
+import { TranslationBlueprint } from '@backstage/plugin-app-react';
+import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
+
+const catalogTranslations = TranslationBlueprint.make({
+ name: 'catalog-overrides',
+ params: {
+ resource: createTranslationMessages({
+ ref: catalogTranslationRef,
+ messages: {
+ 'indexPage.title': 'Service directory',
+ 'indexPage.createButtonTitle': 'Register new service',
+ },
+ }),
+ },
+});
+```
+
+Then install it as a feature in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+
+const app = createApp({
+ features: [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 a translation resource with lazy-loaded message files for each language, and install it using `TranslationBlueprint`:
+
+```ts
+import { createTranslationResource } from '@backstage/frontend-plugin-api';
+import { TranslationBlueprint } from '@backstage/plugin-app-react';
+import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
+
+const userSettingsTranslations = TranslationBlueprint.make({
+ name: 'user-settings-zh',
+ params: {
+ resource: createTranslationResource({
+ ref: userSettingsTranslationRef,
+ translations: {
+ zh: () => import('./userSettings-zh'),
+ },
+ }),
+ },
+});
+```
+
+The translation messages can be defined using `createTranslationMessages` for type safety:
+
+```ts
+// packages/app/src/translations/userSettings-zh.ts
+
+import { createTranslationMessages } from '@backstage/frontend-plugin-api';
+import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
+
+const zh = createTranslationMessages({
+ ref: userSettingsTranslationRef,
+ full: false, // False means that this is a partial translation
+ messages: {
+ 'languageToggle.title': '语言',
+ 'languageToggle.select': '选择{{language}}',
+ },
+});
+
+export default zh;
+```
+
+Or as a plain object export:
+
+```ts
+// packages/app/src/translations/userSettings-zh.ts
+export default {
+ 'languageToggle.title': '语言',
+ 'languageToggle.select': '选择{{language}}',
+ 'languageToggle.description': '切换语言',
+ 'themeToggle.title': '主题',
+ 'themeToggle.description': '切换主题',
+ 'themeToggle.select': '选择{{theme}}',
+ 'themeToggle.selectAuto': '选择自动主题',
+ 'themeToggle.names.auto': '自动',
+ 'themeToggle.names.dark': '暗黑',
+ 'themeToggle.names.light': '明亮',
+};
+```
+
+Install the translation extension in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+
+const app = createApp({
+ features: [userSettingsTranslations],
+});
+```
+
+Go to the Settings page — you should see language switching buttons. Switch languages to verify your translations are loaded correctly.
+
+### Using the CLI for full translation workflows
+
+When translating your app to other languages at scale — especially when working with external translation systems — 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'),
+ },
+ }),
+];
+```
+
+Install the generated resources as features in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+import translationResources from './translations/resources';
+
+const app = createApp({
+ features: translationResources,
+});
+```
+
+#### Custom file patterns
+
+By default, message files use the pattern `messages/{id}.{lang}.json` (e.g. `messages/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/en/catalog.json
+translations/zh/catalog.json
+```
+
+The pattern is stored in the manifest, so the `import` command automatically uses the same layout.
+
+#### Integration with external translation systems
+
+The exported JSON files are standard key-value pairs compatible with most external translation systems. A typical workflow looks like:
+
+1. Run `translations export` to generate the source English files
+2. Upload the `.en.json` files to your translation system
+3. Download the translated files back into the translations 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).
diff --git a/docs/frontend-system/building-plugins/08-analytics.md b/docs/frontend-system/building-plugins/08-analytics.md
new file mode 100644
index 00000000000000..0b51b5ae1ab1da
--- /dev/null
+++ b/docs/frontend-system/building-plugins/08-analytics.md
@@ -0,0 +1,317 @@
+---
+id: analytics
+title: Plugin Analytics
+sidebar_label: Analytics
+description: Measuring usage of your Backstage instance
+---
+
+Setting up, maintaining, and iterating on an instance of Backstage can be a
+large investment. To help measure return on this investment, Backstage comes
+with an event-based Analytics API that grants app integrators the flexibility to
+collect and analyze Backstage usage in the analytics tool of their choice, while
+providing plugin developers a standard interface for instrumenting key user
+interactions.
+
+## Concepts
+
+- **Events** consist of, at a minimum, an `action` (like `click`) and a
+ `subject` (like `thing that was clicked on`).
+- **Attributes** represent additional dimensional data (in the form of key/value
+ pairs) that may be provided on an event-by-event basis. To continue the above
+ example, the URL a user clicked to might look like `{ "to": "/a/page" }`.
+- **Context** represents the broader context in which an event took place. By
+ default, information like `pluginId`, `extension`, and `routeRef` are
+ provided.
+
+This composition of events aims to allow analysis at different levels of detail,
+enabling very granular questions (like "what is the most clicked on thing on a
+particular route") as well as very high-level questions (like "what is the most
+used plugin in my Backstage instance") to be answered.
+
+## Supported Analytics Tools
+
+While all that's needed to consume and forward these events to an analytics tool
+is a concrete implementation of [AnalyticsApi][analytics-api-type], common
+integrations are packaged and provided as plugins. Find your analytics tool of
+choice below.
+
+| Analytics Tool | Support Status |
+| ------------------------------------- | -------------- |
+| [Google Analytics][ga] | Yes ✅ |
+| [Google Analytics 4][ga4] | Yes ✅ |
+| [New Relic Browser][newrelic-browser] | Community ✅ |
+| [Matomo][matomo] | Community ✅ |
+| [Quantum Metric][qm] | Community ✅ |
+| [Generic HTTP][generic-http] | Community ✅ |
+
+To suggest an integration, please [open an issue][add-tool] for the analytics
+tool your organization uses. Or jump to [Writing Integrations][int-howto] to
+learn how to contribute the integration yourself!
+
+[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md
+[ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md
+[newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md
+[qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md
+[matomo]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-matomo/README.md
+[add-tool]: https://github.com/backstage/backstage/issues/new?assignees=&labels=plugin&template=plugin_template.md&title=%5BAnalytics+Module%5D+THE+ANALYTICS+TOOL+TO+INTEGRATE
+[int-howto]: #writing-integrations
+[analytics-api-type]: https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.AnalyticsApi.html
+[generic-http]: https://github.com/pfeifferj/backstage-plugin-analytics-generic/blob/main/README.md
+
+## Key Events
+
+The following table summarizes events that, depending on the plugins you have
+installed, may be captured.
+
+| Action | Subject | Other Notes |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `navigate` | The URL of the page that was navigated to. | Fired immediately when route location changes (unless associated plugin/route data is ambiguous, in which case the event is fired after plugin/route data becomes known, immediately before the next event or document unload). The parameters of the current route will be included as attributes. |
+| `click` | The text of the link that was clicked on. | The `to` attribute represents the URL clicked to. |
+| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`). The `value` represents the number of minutes saved by running the template (based on the template's `backstage.io/time-saved` annotation, if available). |
+| `search` | The search term entered in any search bar component. | The context holds `searchTypes`, representing `types` constraining the search. The `value` represents the total number of search results for the query. This may not be visible if the permission framework is being used. |
+| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided. |
+| `not-found` | The path of the resource that resulted in a not found page | Fired by at least TechDocs. |
+
+If there is an event you'd like to see captured, please [open an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it
+would help you answer. Or jump to [Capturing Events](#capturing-events) to learn how
+to contribute the instrumentation yourself!
+
+_OSS plugin maintainers: feel free to document your events in the table above._
+
+## Writing Integrations
+
+Analytics event forwarding is implemented as a Backstage [Utility API](../utility-apis/01-index.md). The
+provided API need only provide a single method `captureEvent`, which takes
+an `AnalyticsEvent` object.
+
+A simple implementation using `AnalyticsImplementationBlueprint`:
+
+```ts
+import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';
+
+export const acmeAnalyticsImplementation =
+ AnalyticsImplementationBlueprint.make({
+ name: 'acme',
+ params: define =>
+ define({
+ deps: {},
+ factory() {
+ return {
+ captureEvent: event => {
+ window._AcmeAnalyticsQ.push(event);
+ },
+ };
+ },
+ }),
+ });
+```
+
+In reality, you would likely want to encapsulate instantiation logic and pull
+some details from configuration. A more complete example might look like:
+
+```ts
+import {
+ AnalyticsApi,
+ AnalyticsEvent,
+ configApiRef,
+} from '@backstage/frontend-plugin-api';
+import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';
+import { AcmeAnalytics } from 'acme-analytics';
+
+class AcmeAnalyticsImpl implements AnalyticsApi {
+ private constructor(accountId: number) {
+ AcmeAnalytics.init(accountId);
+ }
+
+ static fromConfig(config) {
+ const accountId = config.getString('app.analytics.acme.id');
+ return new AcmeAnalyticsImpl(accountId);
+ }
+
+ captureEvent(event: AnalyticsEvent) {
+ const { action, ...rest } = event;
+ AcmeAnalytics.send(action, rest);
+ }
+}
+
+export const acmeAnalyticsImplementation =
+ AnalyticsImplementationBlueprint.make({
+ name: 'acme',
+ params: define =>
+ define({
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => AcmeAnalyticsImpl.fromConfig(configApi),
+ }),
+ });
+```
+
+If you are integrating with an analytics service (as opposed to an internal
+tool), consider contributing your API implementation as a plugin!
+
+By convention, such packages should be named
+`@backstage/analytics-module-[name]`, and any configuration should be keyed
+under `app.analytics.[name]`.
+
+### Handling User Identity
+
+If the analytics platform you are integrating with has a first-class concept of
+user identity, you can (optionally) choose to support this by the following this
+convention:
+
+- Allow your implementation to be instantiated with the `identityApi` as one of
+ its dependencies.
+- Use the `userEntityRef` resolved by `identityApi`'s `getBackstageIdentity()`
+ method as the basis for the user ID you send to your analytics platform.
+
+## Capturing Events
+
+To instrument an event in a component, start by retrieving an analytics tracker
+using the `useAnalytics()` hook provided by `@backstage/frontend-plugin-api`. The
+tracker includes a `captureEvent` method which takes an `action` and a `subject`
+as arguments.
+
+```ts
+import { useAnalytics } from '@backstage/frontend-plugin-api';
+
+const analytics = useAnalytics();
+analytics.captureEvent('deploy', serviceName);
+```
+
+### Providing Extra Attributes
+
+Additional dimensional `attributes` as well as a numeric `value` can be provided
+on a third `options` argument if/when relevant for the event:
+
+```ts
+analytics.captureEvent('merge', pullRequestName, {
+ value: pullRequestAgeInMinutes,
+ attributes: {
+ org,
+ repo,
+ },
+});
+```
+
+In the above example, an event resembling the following object would be
+captured:
+
+```json
+{
+ "action": "merge",
+ "subject": "Name of Pull Request",
+ "value": 60,
+ "attributes": {
+ "org": "some-org",
+ "repo": "some-repo"
+ }
+}
+```
+
+### Providing Context for Events
+
+The `attributes` option is good for capturing details available to you within
+the component that you're instrumenting. For capturing metadata only available
+further up the react tree, or to help app integrators aggregate distinct events
+by some common value, use an `