I18n: Add useT hook for plugin translation API (#102033)

* Create placeholder useT function

* expose useT() in @grafana/runtime

* fix setting useT correctly

* example usage of useT

* rename hook to useTranslate

* fix rename

* remove comment
This commit is contained in:
Josh Hunt
2025-03-13 13:54:57 +02:00
committed by GitHub
parent f4d43d3ef5
commit 50ff1ae69b
6 changed files with 43 additions and 5 deletions
+5 -1
View File
@@ -887,7 +887,8 @@ exports[`better eslint`] = {
"public/app/app.ts:5381": [
[0, 0, 0, "\'@grafana/runtime/src/components/PanelDataErrorView\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
[0, 0, 0, "\'@grafana/runtime/src/components/PanelRenderer\' import is restricted from being used by a pattern. Import from the public export instead.", "1"],
[0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "2"]
[0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "2"],
[0, 0, 0, "\'@grafana/runtime/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "3"]
],
"public/app/core/TableModel.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -2883,6 +2884,9 @@ exports[`better eslint`] = {
"public/app/features/auth-config/utils/data.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/bookmarks/BookmarksPage.tsx:5381": [
[0, 0, 0, "\'@grafana/runtime/src/unstable\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
"public/app/features/browse-dashboards/api/browseDashboardsAPI.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`@reduxjs/toolkit/query/react\`)", "0"]
],
+1 -2
View File
@@ -9,5 +9,4 @@
* and be subject to the standard policies
*/
// Dummy export to make it a valid module. Remove when we have real exports.
export const unstable = {};
export { useTranslate as useT, setUseTranslateHook } from './utils/i18n';
@@ -0,0 +1,21 @@
type UseTranslateHook = () => (id: string, defaultMessage: string, values?: Record<string, unknown>) => string;
/**
* Provides a i18next-compatible translation function.
*/
export let useTranslate: UseTranslateHook = () => {
// Fallback implementation that should be overridden by setUseT
const errorMessage = 'useT is not set. useT must not be called before Grafana is initialized.';
if (process.env.NODE_ENV === 'development') {
throw new Error(errorMessage);
}
console.error(errorMessage);
return (id: string, defaultMessage: string) => {
return defaultMessage;
};
};
export function setUseTranslateHook(useTParam: UseTranslateHook) {
useTranslate = useTParam;
}
+3 -1
View File
@@ -45,6 +45,7 @@ import {
import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView';
import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer';
import { setPluginPage } from '@grafana/runtime/src/components/PluginPage';
import { setUseTranslateHook } from '@grafana/runtime/src/unstable';
import config, { updateConfig } from 'app/core/config';
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
@@ -58,7 +59,7 @@ import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/componen
import { PluginPage } from './core/components/Page/PluginPage';
import { GrafanaContextType, useChromeHeaderHeight, useReturnToPreviousInternal } from './core/context/GrafanaContext';
import { initializeCrashDetection } from './core/crash';
import { initializeI18n } from './core/internationalization';
import { initializeI18n, useTranslateInternal } from './core/internationalization';
import { setMonacoEnv } from './core/monacoEnv';
import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks';
import { CorrelationsService } from './core/services/CorrelationsService';
@@ -253,6 +254,7 @@ export class GrafanaApp {
setReturnToPreviousHook(useReturnToPreviousInternal);
setChromeHeaderHeightHook(useChromeHeaderHeight);
setUseTranslateHook(useTranslateInternal);
if (config.featureToggles.crashDetection) {
initializeCrashDetection();
@@ -72,6 +72,8 @@ export const Trans = (props: TransProps): ReactElement => {
* This is a simple wrapper over i18n.t() to provide default namespaces and enforce a consistent API.
* Note: Don't use this in the top level module scope. This wrapper needs initialization, which is done during Grafana
* startup, and it will throw if used before.
*
* This will soon be deprecated in favor of useT()
* @param id ID of the translation string
* @param defaultMessage Default message to use if the translation is missing
* @param values Values to be interpolated into the string
@@ -111,3 +113,11 @@ export function getI18next() {
return i18nInstance || i18n;
}
// We want to move to a react-only API for translations.
// This hook doesn't do much now, but we want it to define the API for plugins.
// Perhaps in the future this will use useTranslation from react-i18next or something else
// from context
export function useTranslateInternal() {
return t;
}
@@ -1,15 +1,17 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { useT } from '@grafana/runtime/src/unstable';
import { EmptyState, useStyles2 } from '@grafana/ui';
import { usePinnedItems } from 'app/core/components/AppChrome/MegaMenu/hooks';
import { findByUrl } from 'app/core/components/AppChrome/MegaMenu/utils';
import { NavLandingPageCard } from 'app/core/components/NavLandingPage/NavLandingPageCard';
import { Page } from 'app/core/components/Page/Page';
import { t, Trans } from 'app/core/internationalization';
import { Trans } from 'app/core/internationalization';
import { useSelector } from 'app/types';
export function BookmarksPage() {
const t = useT();
const styles = useStyles2(getStyles);
const pinnedItems = usePinnedItems();
const navTree = useSelector((state) => state.navBarTree);