I18n: Import Scenes translations (#106852)

* wip

* use initScenesTranslations

* comment

* use just plain resource loaded from scenes lib

* update to the published scenes version

* rename loadPluginResources to loadNamespacedResources
pull/107029/head
Josh Hunt 3 days ago committed by GitHub
parent 9a95430815
commit e1af2e15cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      package.json
  2. 10
      packages/grafana-i18n/src/i18n.test.tsx
  3. 9
      packages/grafana-i18n/src/i18n.tsx
  4. 1
      packages/grafana-i18n/src/internal/index.ts
  5. 13
      public/app/app.ts
  6. 4
      public/app/core/internationalization/constants.ts
  7. 24
      yarn.lock

@ -287,8 +287,8 @@
"@grafana/plugin-ui": "0.10.7", "@grafana/plugin-ui": "0.10.7",
"@grafana/prometheus": "workspace:*", "@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*", "@grafana/runtime": "workspace:*",
"@grafana/scenes": "^6.20.1", "@grafana/scenes": "^6.21.0",
"@grafana/scenes-react": "^6.20.1", "@grafana/scenes-react": "^6.21.0",
"@grafana/schema": "workspace:*", "@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*", "@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*", "@grafana/ui": "workspace:*",

@ -4,7 +4,7 @@ import { initReactI18next, setDefaults, setI18n } from 'react-i18next';
import { DEFAULT_LANGUAGE } from './constants'; import { DEFAULT_LANGUAGE } from './constants';
import { import {
loadPluginResources, loadNamespacedResources,
initDefaultI18nInstance, initDefaultI18nInstance,
initDefaultReactI18nInstance, initDefaultReactI18nInstance,
initPluginTranslations, initPluginTranslations,
@ -22,7 +22,7 @@ describe('i18n', () => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
describe('loadPluginResources', () => { describe('loadNamespacedResources', () => {
it('should load all resources for a plugin', async () => { it('should load all resources for a plugin', async () => {
const loaders: ResourceLoader[] = [ const loaders: ResourceLoader[] = [
() => Promise.resolve({ hello: 'Hi' }), () => Promise.resolve({ hello: 'Hi' }),
@ -30,7 +30,7 @@ describe('i18n', () => {
]; ];
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle'); const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
await loadPluginResources('test', 'en-US', loaders); await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(2); expect(addResourceBundleSpy).toHaveBeenCalledTimes(2);
expect(addResourceBundleSpy).toHaveBeenNthCalledWith(1, 'en-US', 'test', { hello: 'Hi' }, true, false); expect(addResourceBundleSpy).toHaveBeenNthCalledWith(1, 'en-US', 'test', { hello: 'Hi' }, true, false);
@ -45,7 +45,7 @@ describe('i18n', () => {
jest.spyOn(console, 'error').mockImplementation(); jest.spyOn(console, 'error').mockImplementation();
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle'); const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
await loadPluginResources('test', 'en-US', loaders); await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(1); expect(addResourceBundleSpy).toHaveBeenCalledTimes(1);
expect(addResourceBundleSpy).toHaveBeenCalledWith('en-US', 'test', { i18n: 'i18n' }, true, false); expect(addResourceBundleSpy).toHaveBeenCalledWith('en-US', 'test', { i18n: 'i18n' }, true, false);
@ -55,7 +55,7 @@ describe('i18n', () => {
const loaders: ResourceLoader[] = []; const loaders: ResourceLoader[] = [];
const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle'); const addResourceBundleSpy = jest.spyOn(i18n, 'addResourceBundle');
await loadPluginResources('test', 'en-US', loaders); await loadNamespacedResources('test', 'en-US', loaders);
expect(addResourceBundleSpy).toHaveBeenCalledTimes(0); expect(addResourceBundleSpy).toHaveBeenCalledTimes(0);
}); });

@ -30,8 +30,7 @@ function initTFuncAndTransComponent({ id, ns }: { id?: string; ns?: string[] } =
transComponent = (props: TransProps) => <I18NextTrans shouldUnescape ns={ns} {...props} />; transComponent = (props: TransProps) => <I18NextTrans shouldUnescape ns={ns} {...props} />;
} }
// exported for testing export async function loadNamespacedResources(namespace: string, language: string, loaders?: ResourceLoader[]) {
export async function loadPluginResources(id: string, language: string, loaders?: ResourceLoader[]) {
if (!loaders?.length) { if (!loaders?.length) {
return; return;
} }
@ -42,9 +41,9 @@ export async function loadPluginResources(id: string, language: string, loaders?
loaders.map(async (loader) => { loaders.map(async (loader) => {
try { try {
const resources = await loader(resolvedLanguage); const resources = await loader(resolvedLanguage);
addResourceBundle(resolvedLanguage, id, resources); addResourceBundle(resolvedLanguage, namespace, resources);
} catch (error) { } 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(); const language = getResolvedLanguage();
initTFuncAndTransComponent({ id }); initTFuncAndTransComponent({ id });
await loadPluginResources(id, language, loaders); await loadNamespacedResources(id, language, loaders);
return { language }; return { language };
} }

@ -17,4 +17,5 @@ export {
getLanguage, getLanguage,
getResolvedLanguage, getResolvedLanguage,
initializeI18n, initializeI18n,
loadNamespacedResources,
} from '../i18n'; } from '../i18n';

@ -19,7 +19,8 @@ import {
standardFieldConfigEditorRegistry, standardFieldConfigEditorRegistry,
standardTransformersRegistry, standardTransformersRegistry,
} from '@grafana/data'; } from '@grafana/data';
import { initializeI18n } from '@grafana/i18n/internal'; import { DEFAULT_LANGUAGE } from '@grafana/i18n';
import { initializeI18n, loadNamespacedResources } from '@grafana/i18n/internal';
import { import {
locationService, locationService,
registerEchoBackend, registerEchoBackend,
@ -49,6 +50,7 @@ import {
setPanelRenderer, setPanelRenderer,
setPluginPage, setPluginPage,
} from '@grafana/runtime/internal'; } from '@grafana/runtime/internal';
import { loadResources as loadScenesResources } from '@grafana/scenes';
import config, { updateConfig } from 'app/core/config'; import config, { updateConfig } from 'app/core/config';
import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; 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 { PluginPage } from './core/components/Page/PluginPage';
import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext'; import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext';
import { initializeCrashDetection } from './core/crash'; 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 { loadTranslations } from './core/internationalization/loadTranslations';
import { postInitTasks, preInitTasks } from './core/lifecycle-hooks'; import { postInitTasks, preInitTasks } from './core/lifecycle-hooks';
import { setMonacoEnv } from './core/monacoEnv'; 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. // 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 // 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.'); // 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); setBackendSrv(backendSrv);
await initEchoSrv(); await initEchoSrv();

@ -5,6 +5,8 @@ import { DEFAULT_LANGUAGE, PSEUDO_LOCALE, LANGUAGES as SUPPORTED_LANGUAGES } fro
export type LocaleFileLoader = () => Promise<ResourceKey>; export type LocaleFileLoader = () => Promise<ResourceKey>;
export const GRAFANA_NAMESPACE = 'grafana' as const;
type BaseLanguageDefinition = (typeof SUPPORTED_LANGUAGES)[number]; type BaseLanguageDefinition = (typeof SUPPORTED_LANGUAGES)[number];
export interface LanguageDefinition<Namespace extends string = string> extends BaseLanguageDefinition { export interface LanguageDefinition<Namespace extends string = string> extends BaseLanguageDefinition {
/** Function to load translations */ /** 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; const locale = def.code === PSEUDO_LOCALE ? DEFAULT_LANGUAGE : def.code;
return { return {
...def, ...def,
loader: { grafana: () => import(`../../../locales/${locale}/grafana.json`) }, loader: { [GRAFANA_NAMESPACE]: () => import(`../../../locales/${locale}/grafana.json`) },
}; };
}); });

@ -3497,11 +3497,11 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@grafana/scenes-react@npm:^6.20.1": "@grafana/scenes-react@npm:^6.21.0":
version: 6.20.2 version: 6.21.1
resolution: "@grafana/scenes-react@npm:6.20.2" resolution: "@grafana/scenes-react@npm:6.21.1"
dependencies: dependencies:
"@grafana/scenes": "npm:6.20.2" "@grafana/scenes": "npm:6.21.1"
lru-cache: "npm:^10.2.2" lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0" react-use: "npm:^17.4.0"
peerDependencies: peerDependencies:
@ -3513,17 +3513,18 @@ __metadata:
react: ^18.0.0 react: ^18.0.0
react-dom: ^18.0.0 react-dom: ^18.0.0
react-router-dom: ^6.28.0 react-router-dom: ^6.28.0
checksum: 10/43bd83d0f265c1e8ac867d0d26d1e970bf9b7d87ff78abd263c51cb2c4af73d536642cbf50d75aa9e1c1af16e9802f1684cc50dfe9f43a8129687ed4831e0706 checksum: 10/2012aae4ce275f428afdadbeabb48eea4faf3045286e68f1e6cc9a02d9118cf2f4094e3f6fce770fc1935f7ac3021362aed9b8a803eefb48e767e38abf0b8679
languageName: node languageName: node
linkType: hard linkType: hard
"@grafana/scenes@npm:6.20.2, @grafana/scenes@npm:^6.20.1": "@grafana/scenes@npm:6.21.1, @grafana/scenes@npm:^6.21.0":
version: 6.20.2 version: 6.21.1
resolution: "@grafana/scenes@npm:6.20.2" resolution: "@grafana/scenes@npm:6.21.1"
dependencies: dependencies:
"@floating-ui/react": "npm:^0.26.16" "@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16" "@leeoniya/ufuzzy": "npm:^1.0.16"
"@tanstack/react-virtual": "npm:^3.9.0" "@tanstack/react-virtual": "npm:^3.9.0"
i18next-parser: "npm:9.3.0"
react-grid-layout: "npm:1.3.4" react-grid-layout: "npm:1.3.4"
react-use: "npm:17.5.0" react-use: "npm:17.5.0"
react-virtualized-auto-sizer: "npm:1.0.24" react-virtualized-auto-sizer: "npm:1.0.24"
@ -3531,13 +3532,14 @@ __metadata:
peerDependencies: peerDependencies:
"@grafana/data": ">=10.4" "@grafana/data": ">=10.4"
"@grafana/e2e-selectors": ">=10.4" "@grafana/e2e-selectors": ">=10.4"
"@grafana/i18n": "*"
"@grafana/runtime": ">=10.4" "@grafana/runtime": ">=10.4"
"@grafana/schema": ">=10.4" "@grafana/schema": ">=10.4"
"@grafana/ui": ">=10.4" "@grafana/ui": ">=10.4"
react: ^18.0.0 react: ^18.0.0
react-dom: ^18.0.0 react-dom: ^18.0.0
react-router-dom: ^6.28.0 react-router-dom: ^6.28.0
checksum: 10/b71416a84773dbe684e348bdb71c8daa507b6e3d84225bc632d191389e8fbd6c7524b1098750bc07d886596786ec47107259966ef9303d603de04012a0ef4d37 checksum: 10/e22f11389a66624d4e5e8addd813902d5c268e4dbe7ee32588c5049b11a540e86d938d52de57ad19a778a211db8f12210138db4ebf09e5ec6e2154522d4ba2da
languageName: node languageName: node
linkType: hard linkType: hard
@ -18138,8 +18140,8 @@ __metadata:
"@grafana/plugin-ui": "npm:0.10.7" "@grafana/plugin-ui": "npm:0.10.7"
"@grafana/prometheus": "workspace:*" "@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*" "@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:^6.20.1" "@grafana/scenes": "npm:^6.21.0"
"@grafana/scenes-react": "npm:^6.20.1" "@grafana/scenes-react": "npm:^6.21.0"
"@grafana/schema": "workspace:*" "@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*" "@grafana/sql": "workspace:*"
"@grafana/test-utils": "workspace:*" "@grafana/test-utils": "workspace:*"

Loading…
Cancel
Save