diff --git a/package.json b/package.json
index de1bc5d8e6f..bb861f07a9d 100644
--- a/package.json
+++ b/package.json
@@ -287,8 +287,8 @@
"@grafana/plugin-ui": "0.10.7",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
- "@grafana/scenes": "^6.20.1",
- "@grafana/scenes-react": "^6.20.1",
+ "@grafana/scenes": "^6.21.0",
+ "@grafana/scenes-react": "^6.21.0",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
diff --git a/packages/grafana-i18n/src/i18n.test.tsx b/packages/grafana-i18n/src/i18n.test.tsx
index d8212cc5f8e..9a1fd349673 100644
--- a/packages/grafana-i18n/src/i18n.test.tsx
+++ b/packages/grafana-i18n/src/i18n.test.tsx
@@ -4,7 +4,7 @@ import { initReactI18next, setDefaults, setI18n } from 'react-i18next';
import { DEFAULT_LANGUAGE } from './constants';
import {
- loadPluginResources,
+ loadNamespacedResources,
initDefaultI18nInstance,
initDefaultReactI18nInstance,
initPluginTranslations,
@@ -22,7 +22,7 @@ describe('i18n', () => {
jest.resetAllMocks();
});
- describe('loadPluginResources', () => {
+ describe('loadNamespacedResources', () => {
it('should load all resources for a plugin', async () => {
const loaders: ResourceLoader[] = [
() => Promise.resolve({ hello: 'Hi' }),
@@ -30,7 +30,7 @@ describe('i18n', () => {
];
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
- await loadPluginResources('test', 'en-US', loaders);
+ await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(2);
expect(addResourceBundleSpy).toHaveBeenNthCalledWith(1, 'en-US', 'test', { hello: 'Hi' }, true, false);
@@ -45,7 +45,7 @@ describe('i18n', () => {
jest.spyOn(console, 'error').mockImplementation();
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
- await loadPluginResources('test', 'en-US', loaders);
+ await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(1);
expect(addResourceBundleSpy).toHaveBeenCalledWith('en-US', 'test', { i18n: 'i18n' }, true, false);
@@ -55,7 +55,7 @@ describe('i18n', () => {
const loaders: ResourceLoader[] = [];
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
- await loadPluginResources('test', 'en-US', loaders);
+ await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(0);
});
diff --git a/packages/grafana-i18n/src/i18n.tsx b/packages/grafana-i18n/src/i18n.tsx
index 6f9ceb0e830..ac22a766d99 100644
--- a/packages/grafana-i18n/src/i18n.tsx
+++ b/packages/grafana-i18n/src/i18n.tsx
@@ -30,8 +30,7 @@ function initTFuncAndTransComponent({ id, ns }: { id?: string; ns?: string[] } =
transComponent = (props: TransProps) => ;
}
-// exported for testing
-export async function loadPluginResources(id: string, language: string, loaders?: ResourceLoader[]) {
+export async function loadNamespacedResources(namespace: string, language: string, loaders?: ResourceLoader[]) {
if (!loaders?.length) {
return;
}
@@ -42,9 +41,9 @@ export async function loadPluginResources(id: string, language: string, loaders?
loaders.map(async (loader) => {
try {
const resources = await loader(resolvedLanguage);
- addResourceBundle(resolvedLanguage, id, resources);
+ addResourceBundle(resolvedLanguage, namespace, resources);
} catch (error) {
- console.error(`Error loading resources for plugin ${id} and language: ${resolvedLanguage}`, error);
+ console.error(`Error loading resources for namespace ${namespace} and language: ${resolvedLanguage}`, error);
}
})
);
@@ -85,7 +84,7 @@ export async function initPluginTranslations(id: string, loaders?: ResourceLoade
const language = getResolvedLanguage();
initTFuncAndTransComponent({ id });
- await loadPluginResources(id, language, loaders);
+ await loadNamespacedResources(id, language, loaders);
return { language };
}
diff --git a/packages/grafana-i18n/src/internal/index.ts b/packages/grafana-i18n/src/internal/index.ts
index 7757d7b622d..bcf80bab663 100644
--- a/packages/grafana-i18n/src/internal/index.ts
+++ b/packages/grafana-i18n/src/internal/index.ts
@@ -17,4 +17,5 @@ export {
getLanguage,
getResolvedLanguage,
initializeI18n,
+ loadNamespacedResources,
} from '../i18n';
diff --git a/public/app/app.ts b/public/app/app.ts
index d934fda7305..ea6400c5837 100644
--- a/public/app/app.ts
+++ b/public/app/app.ts
@@ -19,7 +19,8 @@ import {
standardFieldConfigEditorRegistry,
standardTransformersRegistry,
} from '@grafana/data';
-import { initializeI18n } from '@grafana/i18n/internal';
+import { DEFAULT_LANGUAGE } from '@grafana/i18n';
+import { initializeI18n, loadNamespacedResources } from '@grafana/i18n/internal';
import {
locationService,
registerEchoBackend,
@@ -49,6 +50,7 @@ import {
setPanelRenderer,
setPluginPage,
} from '@grafana/runtime/internal';
+import { loadResources as loadScenesResources } from '@grafana/scenes';
import config, { updateConfig } from 'app/core/config';
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
@@ -63,7 +65,7 @@ import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/componen
import { PluginPage } from './core/components/Page/PluginPage';
import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext';
import { initializeCrashDetection } from './core/crash';
-import { NAMESPACES } from './core/internationalization/constants';
+import { NAMESPACES, GRAFANA_NAMESPACE } from './core/internationalization/constants';
import { loadTranslations } from './core/internationalization/loadTranslations';
import { postInitTasks, preInitTasks } from './core/lifecycle-hooks';
import { setMonacoEnv } from './core/monacoEnv';
@@ -142,7 +144,12 @@ export class GrafanaApp {
// This is a placeholder so we can put a 'comment' in the message json files.
// Starts with an underscore so it's sorted to the top of the file. Even though it is in a comment the following line is still extracted
// t('_comment', 'The code is the source of truth for English phrases. They should be updated in the components directly, and additional plurals specified in this file.');
- initI18nPromise.then(({ language }) => updateConfig({ language }));
+ initI18nPromise.then(async ({ language }) => {
+ updateConfig({ language });
+
+ // Initialise scenes translations into the Grafana namespace. Must finish before any scenes UI is rendered.
+ return loadNamespacedResources(GRAFANA_NAMESPACE, language ?? DEFAULT_LANGUAGE, [loadScenesResources]);
+ });
setBackendSrv(backendSrv);
await initEchoSrv();
diff --git a/public/app/core/internationalization/constants.ts b/public/app/core/internationalization/constants.ts
index 769f79f4a32..c9afaee06f6 100644
--- a/public/app/core/internationalization/constants.ts
+++ b/public/app/core/internationalization/constants.ts
@@ -5,6 +5,8 @@ import { DEFAULT_LANGUAGE, PSEUDO_LOCALE, LANGUAGES as SUPPORTED_LANGUAGES } fro
export type LocaleFileLoader = () => Promise;
+export const GRAFANA_NAMESPACE = 'grafana' as const;
+
type BaseLanguageDefinition = (typeof SUPPORTED_LANGUAGES)[number];
export interface LanguageDefinition extends BaseLanguageDefinition {
/** Function to load translations */
@@ -16,7 +18,7 @@ export const LANGUAGES: LanguageDefinition[] = SUPPORTED_LANGUAGES.map((def) =>
const locale = def.code === PSEUDO_LOCALE ? DEFAULT_LANGUAGE : def.code;
return {
...def,
- loader: { grafana: () => import(`../../../locales/${locale}/grafana.json`) },
+ loader: { [GRAFANA_NAMESPACE]: () => import(`../../../locales/${locale}/grafana.json`) },
};
});
diff --git a/yarn.lock b/yarn.lock
index 0bc699bb79d..a16f1dd50ea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3497,11 +3497,11 @@ __metadata:
languageName: unknown
linkType: soft
-"@grafana/scenes-react@npm:^6.20.1":
- version: 6.20.2
- resolution: "@grafana/scenes-react@npm:6.20.2"
+"@grafana/scenes-react@npm:^6.21.0":
+ version: 6.21.1
+ resolution: "@grafana/scenes-react@npm:6.21.1"
dependencies:
- "@grafana/scenes": "npm:6.20.2"
+ "@grafana/scenes": "npm:6.21.1"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3513,17 +3513,18 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/43bd83d0f265c1e8ac867d0d26d1e970bf9b7d87ff78abd263c51cb2c4af73d536642cbf50d75aa9e1c1af16e9802f1684cc50dfe9f43a8129687ed4831e0706
+ checksum: 10/2012aae4ce275f428afdadbeabb48eea4faf3045286e68f1e6cc9a02d9118cf2f4094e3f6fce770fc1935f7ac3021362aed9b8a803eefb48e767e38abf0b8679
languageName: node
linkType: hard
-"@grafana/scenes@npm:6.20.2, @grafana/scenes@npm:^6.20.1":
- version: 6.20.2
- resolution: "@grafana/scenes@npm:6.20.2"
+"@grafana/scenes@npm:6.21.1, @grafana/scenes@npm:^6.21.0":
+ version: 6.21.1
+ resolution: "@grafana/scenes@npm:6.21.1"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
"@tanstack/react-virtual": "npm:^3.9.0"
+ i18next-parser: "npm:9.3.0"
react-grid-layout: "npm:1.3.4"
react-use: "npm:17.5.0"
react-virtualized-auto-sizer: "npm:1.0.24"
@@ -3531,13 +3532,14 @@ __metadata:
peerDependencies:
"@grafana/data": ">=10.4"
"@grafana/e2e-selectors": ">=10.4"
+ "@grafana/i18n": "*"
"@grafana/runtime": ">=10.4"
"@grafana/schema": ">=10.4"
"@grafana/ui": ">=10.4"
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/b71416a84773dbe684e348bdb71c8daa507b6e3d84225bc632d191389e8fbd6c7524b1098750bc07d886596786ec47107259966ef9303d603de04012a0ef4d37
+ checksum: 10/e22f11389a66624d4e5e8addd813902d5c268e4dbe7ee32588c5049b11a540e86d938d52de57ad19a778a211db8f12210138db4ebf09e5ec6e2154522d4ba2da
languageName: node
linkType: hard
@@ -18138,8 +18140,8 @@ __metadata:
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
- "@grafana/scenes": "npm:^6.20.1"
- "@grafana/scenes-react": "npm:^6.20.1"
+ "@grafana/scenes": "npm:^6.21.0"
+ "@grafana/scenes-react": "npm:^6.21.0"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/test-utils": "workspace:*"