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:*"