Chore: Mark up some content for translations (#96716)

* translate some of core

* more

* translate admin

* fix count translations

* update unit tests
This commit is contained in:
Ashley Harrison 2024-11-21 12:59:14 +00:00 committed by GitHub
parent 4f8ab73a8c
commit c2e1a405b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 928 additions and 516 deletions

View File

@ -794,50 +794,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not re-export imported variable (\`notifyApp\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`hideAppNotification\`)", "3"]
],
"public/app/core/components/AccessControl/PermissionListItem.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/AccessControl/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"]
],
"public/app/core/components/AppChrome/AppChrome.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/AppChrome/TopBar/SignInLink.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/AppNotifications/AppNotificationItem.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/DynamicImports/SafeDynamicImport.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/core/components/EmptyListCTA/EmptyListCTA.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/FolderFilter/FolderFilter.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/ForgottenPassword/ChangePassword.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/core/components/ForgottenPassword/ForgottenPassword.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
],
"public/app/core/components/FormPrompt/FormPrompt.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/core/components/GraphNG/GraphNG.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@ -849,79 +811,14 @@ exports[`better eslint`] = {
"public/app/core/components/LocalStorageValueProvider/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./LocalStorageValueProvider\`)", "0"]
],
"public/app/core/components/Login/LoginLayout.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/Login/LoginServiceButtons.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/NestedFolderPicker/NestedFolderList.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/PageNotFound/EntityNotFound.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/PluginHelp/PluginHelp.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/core/components/RolePicker/BuiltinRoleSelector.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/RolePicker/RolePickerInput.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/RolePicker/RolePickerMenu.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
],
"public/app/core/components/RolePicker/RolePickerSubMenu.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/Select/OldFolderPicker.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/core/components/Signup/SignupPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/core/components/Signup/VerifyEmail.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/core/components/TagFilter/TagFilter.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/core/components/TimeSeries/utils.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/core/components/Upgrade/UpgradeBox.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
],
"public/app/core/components/help/HelpModal.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/core/config.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`Settings\`)", "1"]
@ -936,15 +833,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not re-export imported variable (\`TimeSeries\`)", "6"],
[0, 0, 0, "Do not re-export imported variable (\`updateLegendValues\`)", "7"]
],
"public/app/core/navigation/GrafanaRouteError.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/core/navigation/RouterDebugger.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/core/navigation/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@ -1035,161 +923,18 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/features/actions/ActionEditorModalContent.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/actions/ActionsInlineEditor.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/actions/ParamsEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/admin/AdminEditOrgPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
],
"public/app/features/admin/AdminFeatureTogglesPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/features/admin/AdminFeatureTogglesTable.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/features/admin/AdminListOrgsPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/AdminOrgsTable.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/features/admin/AdminSettings.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/ServerStats.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
],
"public/app/features/admin/UpgradePage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"]
],
"public/app/features/admin/UserCreatePage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/UserLdapSyncInfo.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
],
"public/app/features/admin/UserListAdminPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/UserOrgs.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "11"]
],
"public/app/features/admin/UserPermissions.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/features/admin/UserProfile.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/features/admin/UserSessions.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
],
"public/app/features/admin/Users/AnonUsersTable.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/Users/OrgUsersTable.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
],
"public/app/features/admin/Users/UsersTable.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/admin/ldap/LdapConnectionStatus.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/admin/ldap/LdapPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/admin/ldap/LdapSyncInfo.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/ldap/LdapUserGroups.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/ldap/LdapUserInfo.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/admin/ldap/LdapUserPermissions.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
],
"public/app/features/alerting/routes.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],

View File

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Box, Button, Icon, Select, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { ResourcePermission } from './types';
@ -20,7 +21,13 @@ export const PermissionListItem = ({ item, permissionLevels, canSet, onRemove, o
<tr>
<td>{getAvatar(item)}</td>
<td>{getDescription(item)}</td>
<td>{item.isInherited && <em className={styles.inherited}>Inherited from folder</em>}</td>
<td>
{item.isInherited && (
<em className={styles.inherited}>
<Trans i18nKey="access-control.permission-list-item.inherited">Inherited from folder</Trans>
</em>
)}
</td>
<td>
<Select
disabled={!canSet || !item.isManaged}

View File

@ -7,6 +7,7 @@ import { config, locationSearchToObject, locationService } from '@grafana/runtim
import { useStyles2, LinkButton, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
import { Trans } from 'app/core/internationalization';
import store from 'app/core/store';
import { CommandPalette } from 'app/features/commandPalette/CommandPalette';
import { ScopesDashboards, useScopesDashboardsState } from 'app/features/scopes';
@ -95,7 +96,7 @@ export function AppChrome({ children }: Props) {
{!state.chromeless && (
<>
<LinkButton className={styles.skipLink} href="#pageContent">
Skip to main content
<Trans i18nKey="app-chrome.skip-content-button">Skip to main content</Trans>
</LinkButton>
{isSingleTopNav && menuDockedAndOpen && (
<MegaMenu className={styles.dockedMegaMenu} onClose={() => chrome.setMegaMenuOpen(false)} />

View File

@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom-v5-compat';
import { GrafanaTheme2, locationUtil, textUtil } from '@grafana/data';
import { config } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
export function SignInLink() {
const location = useLocation();
@ -17,7 +18,7 @@ export function SignInLink() {
return (
<a className={styles.link} href={loginUrl} target="_self">
Sign in
<Trans i18nKey="app-chrome.top-bar.sign-in">Sign in</Trans>
</a>
);
}

View File

@ -73,7 +73,7 @@ function PretendTextInput({ onClick }: PretendTextInputProps) {
<div className={styles.suffix}>
<Icon name="keyboard" />
<Text variant="bodySmall">{modKey}+k</Text>
<Text variant="bodySmall">{`${modKey}+k`}</Text>
</div>
</div>
</div>

View File

@ -3,6 +3,7 @@ import { useEffectOnce } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { AppNotification, timeoutMap } from 'app/types';
interface Props {
@ -20,6 +21,7 @@ export default function AppNotificationItem({ appNotification, onClearNotificati
});
const hasBody = appNotification.component || appNotification.text || appNotification.traceId;
const traceId = appNotification.traceId;
return (
<Alert
@ -31,7 +33,11 @@ export default function AppNotificationItem({ appNotification, onClearNotificati
{hasBody && (
<div className={styles.wrapper}>
<span>{appNotification.component || appNotification.text}</span>
{appNotification.traceId && <span className={styles.trace}>Trace ID: {appNotification.traceId}</span>}
{traceId && (
<span className={styles.trace}>
<Trans i18nKey="app-notification.item.trace-id">Trace ID: {{ traceId }}</Trans>
</span>
)}
</div>
)}
</Alert>

View File

@ -3,6 +3,7 @@ import { MouseEvent } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Alert, Button, CallToActionCard, Icon, IconName, LinkButton } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
export interface Props {
title: string;
@ -48,7 +49,7 @@ const EmptyListCTA = ({
{proTip ? (
<span key="proTipFooter">
<Icon name="rocket" />
<> ProTip: {proTip} </>
<Trans i18nKey="empty-list-cta.pro-tip">ProTip: {{ proTip }}</Trans>
{proTipLink && (
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}

View File

@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { AsyncMultiSelect, Icon, Button, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { DashboardSearchItemType } from 'app/features/search/types';
import { FolderInfo, PermissionLevelString } from 'app/types';
@ -40,7 +41,7 @@ export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JS
onClick={() => onChange([])}
aria-label="Clear folders"
>
Clear folders
<Trans i18nKey="folder-filter.clear-folder-button">Clear folders</Trans>
</Button>
)}
<AsyncMultiSelect

View File

@ -4,6 +4,7 @@ import { useForm } from 'react-hook-form';
import { selectors } from '@grafana/e2e-selectors';
import { config } from '@grafana/runtime';
import { Tooltip, Field, Button, Alert, useStyles2, Stack } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { getStyles } from '../Login/LoginForm';
import { PasswordField } from '../PasswordField/PasswordField';
@ -83,7 +84,7 @@ export const ChangePassword = ({ onSubmit, onSkip, showDefaultPasswordWarning }:
</Field>
<Stack direction="column">
<Button type="submit" className={styles.submitButton}>
Submit
<Trans i18nKey="forgot-password.change-password.submit-button">Submit</Trans>
</Button>
{!config.auth.basicAuthStrongPasswordPolicy && onSkip && (
@ -98,7 +99,7 @@ export const ChangePassword = ({ onSubmit, onSkip, showDefaultPasswordWarning }:
type="button"
data-testid={selectors.pages.Login.skip}
>
Skip
<Trans i18nKey="forgot-password.change-password.skip-button">Skip</Trans>
</Button>
</Tooltip>
)}

View File

@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { Field, Input, Button, Legend, Container, useStyles2, LinkButton, Stack } from '@grafana/ui';
import config from 'app/core/config';
import { Trans } from 'app/core/internationalization';
interface EmailDTO {
userOrEmail: string;
@ -40,17 +41,23 @@ export const ForgottenPassword = () => {
if (emailSent) {
return (
<div>
<p>An email with a reset link has been sent to the email address. You should receive it shortly.</p>
<p>
<Trans i18nKey="forgot-password.email-sent">
An email with a reset link has been sent to the email address. You should receive it shortly.
</Trans>
</p>
<Container margin="md" />
<LinkButton variant="primary" href={loginHref}>
Back to login
<Trans i18nKey="forgot-password.back-button">Back to login</Trans>
</LinkButton>
</div>
);
}
return (
<form onSubmit={handleSubmit(sendEmail)}>
<Legend>Reset password</Legend>
<Legend>
<Trans i18nKey="forgot-password.reset-password-header">Reset password</Trans>
</Legend>
<Field
label="User"
description="Enter your information to get a reset link sent to you"
@ -64,13 +71,19 @@ export const ForgottenPassword = () => {
/>
</Field>
<Stack>
<Button type="submit">Send reset email</Button>
<Button type="submit">
<Trans i18nKey="forgot-password.send-email-button">Send reset email</Trans>
</Button>
<LinkButton fill="text" href={loginHref}>
Back to login
<Trans i18nKey="forgot-password.back-button">Back to login</Trans>
</LinkButton>
</Stack>
<p className={styles}>Did you forget your username or email? Contact your Grafana administrator.</p>
<p className={styles}>
<Trans i18nKey="forgot-password.contact-admin">
Did you forget your username or email? Contact your Grafana administrator.
</Trans>
</p>
</form>
);
};

View File

@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { Navigate } from 'react-router-dom-v5-compat';
import { Button, Modal } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Prompt } from './Prompt';
@ -103,13 +104,15 @@ const UnsavedChangesModal = ({ onDiscard, onBackToForm, isOpen }: UnsavedChanges
icon="exclamation-triangle"
className={css({ width: '500px' })}
>
<h5>Changes that you made may not be saved.</h5>
<h5>
<Trans i18nKey="form-prompt.description">Changes that you made may not be saved.</Trans>
</h5>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onBackToForm} fill="outline">
Continue editing
<Trans i18nKey="form-prompt.continue-button">Continue editing</Trans>
</Button>
<Button variant="destructive" onClick={onDiscard}>
Discard unsaved changes
<Trans i18nKey="form-prompt.discard-button">Discard unsaved changes</Trans>
</Button>
</Modal.ButtonRow>
</Modal>

View File

@ -4,6 +4,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Branding } from '../Branding/Branding';
import { BrandingSettings } from '../Branding/types';
@ -44,7 +45,9 @@ export const LoginLayout = ({ children, branding, isChangingPassword }: React.Pr
<Branding.LoginLogo className={loginStyles.loginLogo} logo={loginLogo} />
<div className={loginStyles.titleWrapper}>
{isChangingPassword ? (
<h1 className={loginStyles.mainTitle}>Update your password</h1>
<h1 className={loginStyles.mainTitle}>
<Trans i18nKey="login.layout.update-password">Update your password</Trans>
</h1>
) : (
<>
<h1 className={loginStyles.mainTitle}>{loginTitle}</h1>

View File

@ -113,7 +113,7 @@ const LoginDivider = () => {
<div className={styles.divider.line} />
</div>
<div>
<span>{!config.disableLoginForm && <span>or</span>}</span>
<span>{!config.disableLoginForm && <Trans i18nKey="login.divider.connecting-text">or</Trans>}</span>
</div>
<div>
<div className={styles.divider.line} />

View File

@ -160,9 +160,13 @@ function Row({ index, style: virtualStyles, data }: RowProps) {
}
if (item.kind !== 'folder') {
const itemKind = item.kind;
const itemUID = item.uid;
return process.env.NODE_ENV !== 'production' ? (
<span style={virtualStyles} className={styles.row}>
Non-folder {item.kind} {item.uid}
<Trans i18nKey="browse-dashboards.folder-picker.non-folder-item">
Non-folder {{ itemKind }} {{ itemUID }}
</Trans>
</span>
) : null;
}

View File

@ -3,6 +3,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { EmptyState, TextLink, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
export interface Props {
/**
@ -13,15 +14,18 @@ export interface Props {
export function EntityNotFound({ entity = 'Page' }: Props) {
const styles = useStyles2(getStyles);
const lowerCaseEntity = entity.toLowerCase();
return (
<div className={styles.container} data-testid={selectors.components.EntityNotFound.container}>
<EmptyState message={`${entity} not found`} variant="not-found">
We&apos;re looking but can&apos;t seem to find this {entity.toLowerCase()}. Try returning{' '}
<TextLink href="/">home</TextLink> or seeking help on the{' '}
<TextLink href="https://community.grafana.com" external>
community site.
</TextLink>
<Trans i18nKey="entity-not-found.description">
We&apos;re looking but can&apos;t seem to find this {{ lowerCaseEntity }}. Try returning{' '}
<TextLink href="/">home</TextLink> or seeking help on the{' '}
<TextLink href="https://community.grafana.com" external>
community site.
</TextLink>
</Trans>
</EmptyState>
</div>
);

View File

@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react';
import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data';
import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { getAllPanelPluginMeta } from 'app/features/panel/state/util';
export interface Props {
@ -53,7 +54,7 @@ export const PanelTypeFilter = ({ onChange: propsOnChange, maxMenuHeight }: Prop
onClick={() => onChange([])}
aria-label="Clear types"
>
Clear types
<Trans i18nKey="panel-type-filter.clear-button">Clear types</Trans>
</Button>
)}
<MultiSelect<PanelPluginMeta> {...selectOptions} prefix={<Icon name="filter" />} aria-label="Panel Type filter" />

View File

@ -3,6 +3,7 @@ import { useAsync } from 'react-use';
import { renderMarkdown } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { LoadingPlaceholder } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
interface Props {
pluginId: string;
@ -20,11 +21,19 @@ export function PluginHelp({ pluginId }: Props) {
}
if (error) {
return <h3>An error occurred when loading help.</h3>;
return (
<h3>
<Trans i18nKey="plugins.plugin-help.error">An error occurred when loading help.</Trans>
</h3>
);
}
if (value === '') {
return <h3>No query help could be found.</h3>;
return (
<h3>
<Trans i18nKey="plugins.plugin-help.not-found">No query help could be found.</Trans>
</h3>
);
}
return <div className="markdown-html" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />;

View File

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data';
import { Icon, RadioButtonList, Tooltip, useStyles2, useTheme2, PopoverContent } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { OrgRole } from 'app/types';
import { getStyles } from './styles';
@ -24,7 +25,9 @@ export const BuiltinRoleSelector = ({ value, onChange, disabled, disabledMesssag
return (
<>
<div className={styles.groupHeader}>
<span style={{ marginRight: theme.spacing(1) }}>Basic roles</span>
<span style={{ marginRight: theme.spacing(1) }}>
<Trans i18nKey="role-picker.built-in.basic-roles">Basic roles</Trans>
</span>
{disabled && disabledMesssage && (
<Tooltip placement="right-end" interactive={true} content={<div>{disabledMesssage}</div>}>
<Icon name="question-circle" />

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, getInputStyles, sharedInputStyle, Tooltip, Icon, Spinner } from '@grafana/ui';
import { getFocusStyles } from '@grafana/ui/src/themes/mixins';
import { Trans } from 'app/core/internationalization';
import { Role } from '../../../types';
@ -125,7 +126,11 @@ export const RolesLabel = ({ showBuiltInRole, numberOfRoles, appliedRoles }: Rol
}`}</ValueContainer>
</Tooltip>
) : (
!showBuiltInRole && <ValueContainer>No roles assigned</ValueContainer>
!showBuiltInRole && (
<ValueContainer>
<Trans i18nKey="role-picker.input.no-roles">No roles assigned</Trans>
</ValueContainer>
)
)}
</>
);

View File

@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import { Button, ScrollContainer, Stack, TextLink, useStyles2, useTheme2 } from '@grafana/ui';
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
import { Trans } from 'app/core/internationalization';
import { OrgRole, Role } from 'app/types';
import { BuiltinRoleSelector } from './BuiltinRoleSelector';
@ -35,7 +36,7 @@ const fixedRoleGroupNames: Record<string, string> = {
};
const tooltipMessage = (
<>
<Trans i18nKey="role-picker.menu.tooltip">
You can now select the &quot;No basic role&quot; option and add permissions to your custom needs. You can find more
information in&nbsp;
<TextLink
@ -46,7 +47,7 @@ const tooltipMessage = (
our documentation
</TextLink>
.
</>
</Trans>
);
interface RolePickerMenuProps {
@ -275,7 +276,7 @@ export const RolePickerMenu = ({
<div className={customStyles.menuButtonRow}>
<Stack justifyContent="flex-end">
<Button size="sm" fill="text" onClick={onClearInternal} disabled={updateDisabled}>
Clear all
<Trans i18nKey="role-picker.menu.clear-button">Clear all</Trans>
</Button>
<Button size="sm" onClick={onUpdateInternal} disabled={updateDisabled}>
{apply ? `Apply` : `Update`}

View File

@ -2,6 +2,7 @@ import { cx } from '@emotion/css';
import { Button, ScrollContainer, Stack, useStyles2, useTheme2 } from '@grafana/ui';
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
import { Trans } from 'app/core/internationalization';
import { Role } from 'app/types';
import { RoleMenuOption } from './RoleMenuOption';
@ -67,7 +68,7 @@ export const RolePickerSubMenu = ({
<div className={customStyles.subMenuButtonRow}>
<Stack justifyContent="flex-end">
<Button size="sm" fill="text" onClick={onClearInternal}>
Clear
<Trans i18nKey="role-picker.sub-menu.clear-button">Clear</Trans>
</Button>
</Stack>
</div>

View File

@ -9,7 +9,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import { ActionMeta, AsyncVirtualizedSelect, Input, InputActionMeta, useStyles2 } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { t } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { createFolder, getFolderByUid, searchFolders } from 'app/features/manage-dashboards/state/actions';
import { DashboardSearchHit } from 'app/features/search/types';
@ -323,7 +323,9 @@ export function OldFolderPicker(props: Props) {
return (
<>
<FolderWarningWhenCreating />
<div className={styles.newFolder}>Press enter to create the new folder.</div>
<div className={styles.newFolder}>
<Trans i18nKey="folder-picker.create-instructions">Press enter to create the new folder.</Trans>
</div>
<Input
width={30}
autoFocus={true}

View File

@ -4,6 +4,7 @@ import { getBackendSrv } from '@grafana/runtime';
import { Field, Input, Button, LinkButton, Stack } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { useAppNotification } from 'app/core/copy/appNotification';
import { Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { w3cStandardEmailValidator } from 'app/features/admin/utils';
@ -113,9 +114,11 @@ export const SignupPage = ({ queryParams }: Props) => {
</Field>
<Stack>
<Button type="submit">Submit</Button>
<Button type="submit">
<Trans i18nKey="sign-up.submit-button">Submit</Trans>
</Button>
<LinkButton fill="text" href={getConfig().appSubUrl + '/login'}>
Back to login
<Trans i18nKey="sign-up.back-button">Back to login</Trans>
</LinkButton>
</Stack>
</form>

View File

@ -5,6 +5,7 @@ import { getBackendSrv } from '@grafana/runtime';
import { Field, Input, Button, Legend, Container, LinkButton, Stack } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { useAppNotification } from 'app/core/copy/appNotification';
import { Trans } from 'app/core/internationalization';
import { w3cStandardEmailValidator } from 'app/features/admin/utils';
interface EmailDTO {
@ -35,10 +36,14 @@ export const VerifyEmail = () => {
if (emailSent) {
return (
<div>
<p>An email with a verification link has been sent to the email address. You should receive it shortly.</p>
<p>
<Trans i18nKey="sign-up.verify.info">
An email with a verification link has been sent to the email address. You should receive it shortly.
</Trans>
</p>
<Container margin="md" />
<LinkButton variant="primary" href={getConfig().appSubUrl + '/signup'}>
Complete Signup
<Trans i18nKey="sign-up.verify.complete-button">Complete signup</Trans>
</LinkButton>
</div>
);
@ -46,7 +51,9 @@ export const VerifyEmail = () => {
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Legend>Verify Email</Legend>
<Legend>
<Trans i18nKey="sign-up.verify.header">Verify email</Trans>
</Legend>
<Field
label="Email"
description="Enter your email address to get a verification link sent to you"
@ -66,9 +73,11 @@ export const VerifyEmail = () => {
/>
</Field>
<Stack>
<Button type="submit">Send verification email</Button>
<Button type="submit">
<Trans i18nKey="sign-up.verify.send-button">Send verification email</Trans>
</Button>
<LinkButton fill="text" href={getConfig().appSubUrl + '/login'}>
Back to login
<Trans i18nKey="sign-up.verify.back-button">Back to login</Trans>
</LinkButton>
</Stack>
</form>

View File

@ -28,7 +28,7 @@ jest.mock('@grafana/runtime', () => ({
describe('VerifyEmail Page', () => {
it('renders correctly', () => {
render(<VerifyEmailPage />);
expect(screen.getByText('Verify Email')).toBeInTheDocument();
expect(screen.getByText('Verify email')).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: /Email/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Send verification email' })).toBeInTheDocument();
@ -60,7 +60,7 @@ describe('VerifyEmail Page', () => {
email: 'test@gmail.com',
})
);
expect(screen.getByRole('link', { name: 'Complete Signup' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Complete Signup' })).toHaveAttribute('href', '/signup');
expect(screen.getByRole('link', { name: 'Complete signup' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Complete signup' })).toHaveAttribute('href', '/signup');
});
});

View File

@ -4,7 +4,7 @@ import { components, MultiValueRemoveProps } from 'react-select';
import { escapeStringForRegex, GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Icon, MultiSelect, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { TagBadge, getStyles as getTagBadgeStyles } from './TagBadge';
import { TagOption, TagSelectOption } from './TagOption';
@ -156,7 +156,7 @@ export const TagFilter = ({
<div className={styles.tagFilter}>
{isClearable && tags.length > 0 && (
<button className={styles.clear} onClick={() => onTagChange([])}>
Clear tags
<Trans i18nKey="tag-filter.clear-button">Clear tags</Trans>
</button>
)}
<MultiSelect key={selectKey} {...selectOptions} prefix={<Icon name="tag-alt" />} aria-label="Tag filter" />

View File

@ -4,6 +4,7 @@ import { HTMLAttributes, useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { reportExperimentView } from '@grafana/runtime/src';
import { Button, Icon, LinkButton, useStyles2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
type ComponentSize = 'sm' | 'md';
@ -36,7 +37,11 @@ export const UpgradeBox = ({
<Icon name={'rocket'} className={styles.icon} />
<div className={styles.inner}>
<p className={styles.text}>
Youve discovered a Pro feature! {text || `Get the Grafana Pro plan to access ${featureName}.`}
<Trans i18nKey="upgrade-box.discovery-text">Youve discovered a Pro feature!</Trans>{' '}
{text ||
t('upgrade-box.discovery-text-continued', 'Get the Grafana Pro plan to access {{featureName}}.', {
featureName,
})}
</p>
<LinkButton
variant="secondary"
@ -46,7 +51,7 @@ export const UpgradeBox = ({
target="__blank"
rel="noopener noreferrer"
>
Upgrade
<Trans i18nKey="upgrade-box.upgrade-button">Upgrade</Trans>
</LinkButton>
</div>
</div>
@ -130,7 +135,9 @@ export const UpgradeContent = ({
return (
<div className={styles.container}>
<div className={styles.content}>
<h3 className={styles.title}>Get started with {featureName}</h3>
<h3 className={styles.title}>
<Trans i18nKey="upgrade-box.get-started">Get started with {{ featureName }}</Trans>
</h3>
{description && <h6 className={styles.description}>{description}</h6>}
<ul className={styles.list}>
{listItems.map((item, index) => (
@ -151,7 +158,7 @@ export const UpgradeContent = ({
)}
{featureUrl && (
<LinkButton fill={'text'} href={featureUrl} className={styles.link} target="_blank" rel="noreferrer noopener">
Learn more
<Trans i18nKey="upgrade-box.learn-more">Learn more</Trans>
</LinkButton>
)}
</div>
@ -221,10 +228,12 @@ export const UpgradeContentVertical = ({
const styles = useStyles2(getContentVerticalStyles);
return (
<div className={styles.container}>
<h3 className={styles.title}>Get started with {featureName}</h3>
<h3 className={styles.title}>
<Trans i18nKey="upgrade-box.get-started">Get started with {{ featureName }}</Trans>
</h3>
{description && <h6 className={styles.description}>{description}</h6>}
<LinkButton fill={'text'} href={featureUrl} target="_blank" rel="noreferrer noopener">
Learn more
<Trans i18nKey="upgrade-box.learn-more">Learn more</Trans>
</LinkButton>
<div className={styles.media}>
<img src={getImgUrl(image)} alt={'Feature screenshot'} />

View File

@ -3,7 +3,7 @@ import { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Grid, Modal, useStyles2, Text } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { getModKey } from 'app/core/utils/browser';
const getShortcuts = (modKey: string) => {
@ -165,8 +165,12 @@ export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
</caption>
<thead className="sr-only">
<tr>
<th>Keys</th>
<th>Description</th>
<th>
<Trans i18nKey="help-modal.column-headers.keys">Keys</Trans>
</th>
<th>
<Trans i18nKey="help-modal.column-headers.description">Description</Trans>
</th>
</tr>
</thead>
<tbody>

View File

@ -6,6 +6,7 @@ import { GrafanaTheme2, locationUtil, PageLayoutType } from '@grafana/data';
import { Button, ErrorWithStack, useStyles2 } from '@grafana/ui';
import { Page } from '../components/Page/Page';
import { Trans } from '../internationalization';
interface Props {
error: Error | null;
@ -31,12 +32,18 @@ export function GrafanaRouteError({ error, errorInfo }: Props) {
<div className={styles.container}>
{isChunkLoadingError && (
<div>
<h2>Unable to find application file</h2>
<h2>
<Trans i18nKey="route-error.title">Unable to find application file</Trans>
</h2>
<br />
<h2 className="page-heading">Grafana has likely been updated. Please try reloading the page.</h2>
<h2 className="page-heading">
<Trans i18nKey="route-error.description">
Grafana has likely been updated. Please try reloading the page.
</Trans>
</h2>
<br />
<Button size="md" variant="secondary" icon="repeat" onClick={() => window.location.reload()}>
Reload
<Trans i18nKey="route-error.reload-button">Reload</Trans>
</Button>
<ErrorWithStack title={'Error details'} error={error} errorInfo={errorInfo} />
</div>

View File

@ -1,46 +0,0 @@
import { Link } from 'react-router-dom-v5-compat';
import { getAppRoutes } from '../../routes/routes';
import { PageContents } from '../components/Page/PageContents';
import { RouteDescriptor } from './types';
export const RouterDebugger = () => {
const manualRoutes: RouteDescriptor[] = [];
return (
<PageContents>
<h1>Static routes</h1>
<ul>
{getAppRoutes().map((r, i) => {
if (r.path.indexOf(':') > -1 || r.path.indexOf('test') > -1) {
if (r.path.indexOf('test') === -1) {
manualRoutes.push(r);
}
return null;
}
return (
<li key={i}>
<Link target="_blank" to={r.path}>
{r.path}
</Link>
</li>
);
})}
</ul>
<h1>Dynamic routes - check those manually</h1>
<ul>
{manualRoutes.map((r, i) => {
return (
<li key={i}>
<Link key={`${i}-${r.path}`} target="_blank" to={r.path}>
{r.path}
</Link>
</li>
);
})}
</ul>
</PageContents>
);
};

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Action, DataFrame, VariableSuggestion } from '@grafana/data';
import { Button } from '@grafana/ui/src/components/Button';
import { Modal } from '@grafana/ui/src/components/Modal/Modal';
import { Trans } from 'app/core/internationalization';
import { ActionEditor } from './ActionEditor';
@ -36,7 +37,7 @@ export const ActionEditorModalContent = ({
/>
<Modal.ButtonRow>
<Button variant="secondary" onClick={() => onCancel(index)} fill="outline">
Cancel
<Trans i18nKey="action-editor.modal.cancel-button">Cancel</Trans>
</Button>
<Button
onClick={() => {
@ -44,7 +45,7 @@ export const ActionEditorModalContent = ({
}}
disabled={dirtyAction.title.trim() === '' || dirtyAction.fetch.url.trim() === ''}
>
Save
<Trans i18nKey="action-editor.modal.save-button">Save</Trans>
</Button>
</Modal.ButtonRow>
</>

View File

@ -7,6 +7,7 @@ import { Action, DataFrame, GrafanaTheme2, defaultActionConfig, VariableSuggesti
import { Button } from '@grafana/ui/src/components/Button';
import { Modal } from '@grafana/ui/src/components/Modal/Modal';
import { useStyles2 } from '@grafana/ui/src/themes';
import { Trans } from 'app/core/internationalization';
import { ActionEditorModalContent } from './ActionEditorModalContent';
import { ActionListItem } from './ActionsListItem';
@ -95,7 +96,9 @@ export const ActionsInlineEditor = ({
{/* one-link placeholder */}
{showOneClick && actionsSafe.length > 0 && (
<div className={styles.oneClickOverlay}>
<span className={styles.oneClickSpan}>One-click link</span>
<span className={styles.oneClickSpan}>
<Trans i18nKey="actions-editor.inline.one-click-link">One-click link</Trans>
</span>
</div>
)}
@ -151,7 +154,7 @@ export const ActionsInlineEditor = ({
)}
<Button size="sm" icon="plus" onClick={onActionAdd} variant="secondary" className={styles.button}>
Add action
<Trans i18nKey="actions-editor.inline.add-button">Add action</Trans>
</Button>
</div>
);

View File

@ -7,6 +7,7 @@ import { NavModelItem } from '@grafana/data';
import { Field, Input, Button, Legend, Alert } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { OrgUser, AccessControlAction, OrgRole } from 'app/types';
import { OrgUsersTable } from './Users/OrgUsersTable';
@ -56,8 +57,10 @@ const AdminEditOrgPage = () => {
const renderMissingPermissionMessage = () => (
<Alert severity="info" title="Access denied">
You do not have permission to see users in this organization. To update this organization, contact your server
administrator.
<Trans i18nKey="admin.edit-org.access-denied">
You do not have permission to see users in this organization. To update this organization, contact your server
administrator.
</Trans>
</Alert>
);
@ -85,7 +88,9 @@ const AdminEditOrgPage = () => {
<Page navId="global-orgs" pageNav={pageNav} subTitle="Manage settings for this specific org.">
<Page.Contents>
<>
<Legend>Edit organization</Legend>
<Legend>
<Trans i18nKey="admin.edit-org.heading">Edit Organization</Trans>
</Legend>
{orgState.value && (
<form onSubmit={handleSubmit(onUpdateOrgName)} style={{ maxWidth: '600px' }}>
<Field label="Name" invalid={!!errors.orgName} error="Name is required" disabled={!canWriteOrg}>
@ -96,13 +101,15 @@ const AdminEditOrgPage = () => {
/>
</Field>
<Button type="submit" disabled={!canWriteOrg}>
Update
<Trans i18nKey="admin.edit-org.update-button">Update</Trans>
</Button>
</form>
)}
<div style={{ marginTop: '20px' }}>
<Legend>Organization users</Legend>
<Legend>
<Trans i18nKey="admin.edit-org.users-heading">Organization users</Trans>
</Legend>
{!canReadUsers && renderMissingPermissionMessage()}
{canReadUsers && !!users.length && (
<OrgUsersTable

View File

@ -5,6 +5,7 @@ import { useAsync } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, Icon } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import { getTogglesAPI } from './AdminFeatureTogglesAPI';
import { AdminFeatureTogglesTable } from './AdminFeatureTogglesTable';
@ -36,15 +37,17 @@ export default function AdminFeatureTogglesPage() {
const subTitle = (
<div>
View and edit feature toggles. Read more about feature toggles at{' '}
<a
className="external-link"
target="_new"
href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/"
>
grafana.com
</a>
.
<Trans i18nKey="admin.feature-toggles.sub-title">
View and edit feature toggles. Read more about feature toggles at{' '}
<a
className="external-link"
target="_new"
href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/"
>
grafana.com
</a>
.
</Trans>
</div>
);

View File

@ -4,6 +4,7 @@ import useAsyncFn from 'react-use/lib/useAsyncFn';
import { getBackendSrv, isFetchError } from '@grafana/runtime';
import { LinkButton } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction, Organization } from 'app/types';
@ -34,7 +35,7 @@ export default function AdminListOrgsPages() {
navId="global-orgs"
actions={
<LinkButton icon="plus" href="org/new" disabled={!canCreateOrg}>
New org
<Trans i18nKey="admin.orgs.new-org-button">New org</Trans>
</LinkButton>
}
>

View File

@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Button, ConfirmModal, useStyles2 } from '@grafana/ui';
import { SkeletonComponent, attachSkeleton } from '@grafana/ui/src/unstable';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { AccessControlAction, Organization } from 'app/types';
interface Props {
@ -16,8 +17,12 @@ interface Props {
const getTableHeader = () => (
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>
<Trans i18nKey="admin.orgs.id-header">ID</Trans>
</th>
<th>
<Trans i18nKey="admin.orgs.name-header">Name</Trans>
</th>
<th style={{ width: '1%' }}></th>
</tr>
</thead>
@ -27,6 +32,7 @@ function AdminOrgsTableComponent({ orgs, onDelete }: Props) {
const canDeleteOrgs = contextSrv.hasPermission(AccessControlAction.OrgsDelete);
const [deleteOrg, setDeleteOrg] = useState<Organization>();
const deleteOrgName = deleteOrg?.name;
return (
<table className="filter-table form-inline filter-table--hover">
{getTableHeader()}
@ -59,8 +65,10 @@ function AdminOrgsTableComponent({ orgs, onDelete }: Props) {
title="Delete"
body={
<div>
Are you sure you want to delete &apos;{deleteOrg.name}&apos;?
<br /> <small>All dashboards for this organization will be removed!</small>
<Trans i18nKey="admin.orgs.delete-body">
Are you sure you want to delete &apos;{{ deleteOrgName }}&apos;?
<br /> <small>All dashboards for this organization will be removed!</small>
</Trans>
</div>
}
confirmText="Delete"

View File

@ -3,6 +3,7 @@ import { useAsync } from 'react-use';
import { getBackendSrv } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import { AdminSettingsTable } from './AdminSettingsTable';
@ -15,8 +16,10 @@ function AdminSettings() {
<Page navId="server-settings">
<Page.Contents>
<Alert severity="info" title="">
These system settings are defined in grafana.ini or custom.ini (or overridden in ENV variables). To change
these you currently need to restart Grafana.
<Trans i18nKey="admin.settings.info-description">
These system settings are defined in grafana.ini or custom.ini (or overridden in ENV variables). To change
these you currently need to restart Grafana.
</Trans>
</Alert>
{loading && <AdminSettingsTable.Skeleton />}

View File

@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { config, GrafanaBootConfig } from '@grafana/runtime';
import { LinkButton, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { AccessControlAction } from 'app/types';
import { contextSrv } from '../../core/services/context_srv';
@ -34,9 +35,13 @@ export const ServerStats = () => {
return (
<>
<h2 className={styles.title}>Instance statistics</h2>
<h2 className={styles.title}>
<Trans i18nKey="admin.server-settings.title">Instance statistics</Trans>
</h2>
{!isLoading && !stats ? (
<p className={styles.notFound}>No stats found.</p>
<p className={styles.notFound}>
<Trans i18nKey="admin.server-settings.not-found">No stats found.</Trans>
</p>
) : (
<div className={styles.row}>
<ServerStatsCard
@ -49,7 +54,7 @@ export const ServerStats = () => {
]}
footer={
<LinkButton href={'/dashboards'} variant={'secondary'}>
Manage dashboards
<Trans i18nKey="admin.server-settings.dashboards-button">Manage dashboards</Trans>
</LinkButton>
}
/>
@ -61,7 +66,7 @@ export const ServerStats = () => {
footer={
hasAccessToDataSources && (
<LinkButton href={'/datasources'} variant={'secondary'}>
Manage data sources
<Trans i18nKey="admin.server-settings.data-sources-button">Manage data sources</Trans>
</LinkButton>
)
}
@ -71,7 +76,7 @@ export const ServerStats = () => {
content={[{ name: 'Alerts', value: stats?.alerts }]}
footer={
<LinkButton href={'/alerting/list'} variant={'secondary'}>
Manage alerts
<Trans i18nKey="admin.server-settings.alerts-button">Manage alerts</Trans>
</LinkButton>
}
/>
@ -88,7 +93,7 @@ export const ServerStats = () => {
footer={
hasAccessToAdminUsers && (
<LinkButton href={'/admin/users'} variant={'secondary'}>
Manage users
<Trans i18nKey="admin.server-settings.users-button">Manage users</Trans>
</LinkButton>
)
}

View File

@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { GrafanaTheme2, NavModel } from '@grafana/data';
import { LinkButton, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import { getNavModel } from '../../core/selectors/navModel';
import { StoreState } from '../../types';
@ -41,7 +42,9 @@ export const UpgradeInfo = ({ editionNotice }: UpgradeInfoProps) => {
return (
<>
<h2 className={styles.title}>Enterprise license</h2>
<h2 className={styles.title}>
<Trans i18nKey="admin.upgrade-info.title">Enterprise license</Trans>
</h2>
<LicenseChrome header="Grafana Enterprise" subheader="Get your free trial" editionNotice={editionNotice}>
<div className={styles.column}>
<FeatureInfo />
@ -73,11 +76,15 @@ const getStyles = (theme: GrafanaTheme2) => {
const GetEnterprise = () => {
return (
<div style={{ marginTop: '40px', marginBottom: '30px' }}>
<h2 style={titleStyles}>Get Grafana Enterprise</h2>
<h2 style={titleStyles}>
<Trans i18nKey="admin.get-enterprise.title">Get Grafana Enterprise</Trans>
</h2>
<CallToAction />
<p style={{ paddingTop: '12px' }}>
You can use the trial version for free for 30 days. We will remind you about it five days before the trial
period ends.
<Trans i18nKey="admin.get-enterprise.description">
You can use the trial version for free for 30 days. We will remind you about it five days before the trial
period ends.
</Trans>
</p>
</div>
);
@ -90,7 +97,7 @@ const CallToAction = () => {
size="lg"
href="https://grafana.com/contact?about=grafana-enterprise&utm_source=grafana-upgrade-page"
>
Contact us and get a free trial
<Trans i18nKey="admin.get-enterprise.contact-us">Contact us and get a free trial</Trans>
</LinkButton>
);
};
@ -98,7 +105,9 @@ const CallToAction = () => {
const ServiceInfo = () => {
return (
<div>
<h4>At your service</h4>
<h4>
<Trans i18nKey="admin.get-enterprise.service-title">At your service</Trans>
</h4>
<List>
<Item title="Enterprise Plugins" image="public/img/licensing/plugin_enterprise.svg" />
@ -117,9 +126,13 @@ const ServiceInfo = () => {
</List>
<div style={{ marginTop: '20px' }}>
<strong>Also included:</strong>
<strong>
<Trans i18nKey="admin.get-enterprise.included-heading">Also included:</Trans>
</strong>
<br />
Indemnification, working with Grafana Labs on future prioritization, and training from the core Grafana team.
<Trans i18nKey="admin.get-enterprise.included-description">
Indemnification, working with Grafana Labs on future prioritization, and training from the core Grafana team.
</Trans>
</div>
<GetEnterprise />
@ -130,7 +143,9 @@ const ServiceInfo = () => {
const FeatureInfo = () => {
return (
<div style={{ paddingRight: '11px' }}>
<h4>Enhanced functionality</h4>
<h4>
<Trans i18nKey="admin.get-enterprise.features-heading">Enhanced functionality</Trans>
</h4>
<FeatureListing />
</div>
);
@ -143,7 +158,9 @@ const FeatureListing = () => {
<Item title="Reporting" />
<Item title="SAML authentication" />
<Item title="Enhanced LDAP integration" />
<Item title="Team Sync">LDAP, GitHub OAuth, Auth Proxy, Okta</Item>
<Item title="Team Sync">
<Trans i18nKey="admin.get-enterprise.team-sync-details">LDAP, GitHub OAuth, Auth Proxy, Okta</Trans>
</Item>
<Item title="White labeling" />
<Item title="Auditing" />
<Item title="Settings updates at runtime" />

View File

@ -6,6 +6,7 @@ import { NavModelItem } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { Button, Input, Field } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
interface UserDTO {
name: string;
@ -69,7 +70,9 @@ const UserCreatePage = () => {
type="password"
/>
</Field>
<Button type="submit">Create user</Button>
<Button type="submit">
<Trans i18nKey="admin.users-create.create-button">Create user</Trans>
</Button>
</form>
</Page.Contents>
</Page>

View File

@ -3,6 +3,7 @@ import { PureComponent } from 'react';
import { dateTimeFormat } from '@grafana/data';
import { Button, LinkButton } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { AccessControlAction, SyncInfo, UserDTO } from 'app/types';
import { TagBadge } from '../../core/components/TagFilter/TagBadge';
@ -33,30 +34,37 @@ export class UserLdapSyncInfo extends PureComponent<Props, State> {
return (
<>
<h3 className="page-heading">LDAP Synchronisation</h3>
<h3 className="page-heading">
<Trans i18nKey="admin.ldap-sync.title">LDAP Synchronisation</Trans>
</h3>
<div className="gf-form-group">
<div className="gf-form">
<table className="filter-table form-inline">
<tbody>
<tr>
<td>External sync</td>
<td>User synced via LDAP. Some changes must be done in LDAP or mappings.</td>
<td>
<Trans i18nKey="admin.ldap-sync.external-sync-label">External sync</Trans>
</td>
<td>
<Trans i18nKey="admin.ldap-sync.external-sync-description">
User synced via LDAP. Some changes must be done in LDAP or mappings.
</Trans>
</td>
<td>
<TagBadge label="LDAP" removeIcon={false} count={0} onClick={undefined} />
</td>
</tr>
<tr>
{ldapSyncInfo.enabled ? (
<>
<td>Next scheduled synchronization</td>
<td colSpan={2}>{nextSyncTime}</td>
</>
) : (
<>
<td>Next scheduled synchronization</td>
<td colSpan={2}>Not enabled</td>
</>
)}
<td>
<Trans i18nKey="admin.ldap-sync.next-sync-label">Next scheduled synchronization</Trans>
</td>
<td colSpan={2}>
{ldapSyncInfo.enabled ? (
nextSyncTime
) : (
<Trans i18nKey="admin.ldap-sync.not-enabled">Not enabled</Trans>
)}
</td>
</tr>
</tbody>
</table>
@ -64,12 +72,12 @@ export class UserLdapSyncInfo extends PureComponent<Props, State> {
<div className="gf-form-button-row">
{canSyncLDAPUser && (
<Button variant="secondary" onClick={this.onUserSync}>
Sync user
<Trans i18nKey="admin.ldap-sync.sync-button">Sync user</Trans>
</Button>
)}
{canReadLDAPUser && (
<LinkButton variant="secondary" href={debugLDAPMappingURL}>
Debug LDAP Mapping
<Trans i18nKey="admin.ldap-sync.debug-button">Debug LDAP Mapping</Trans>
</LinkButton>
)}
</div>

View File

@ -7,7 +7,7 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { LinkButton, RadioButtonGroup, useStyles2, FilterInput, EmptyState } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
import { t } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { AccessControlAction, StoreState, UserFilter } from '../../types';
@ -94,7 +94,7 @@ const UserListAdminPageUnConnected = ({
))}
{contextSrv.hasPermission(AccessControlAction.UsersCreate) && (
<LinkButton href="admin/users/create" variant="primary">
New user
<Trans i18nKey="admin.users-list.create-button">New user</Trans>
</LinkButton>
)}
</div>

View File

@ -57,7 +57,7 @@ export const DashboardsListModal = ({ email, onDismiss }: { email: string; onDis
</Trans>
)}
</a>
<span className={styles.urlsDivider}></span>
<span className={styles.urlsDivider}>{'•'}</span>
<a
className={cx('external-link', styles.url)}
href={generatePublicDashboardConfigUrl(dash.dashboardUid, dash.slug)}

View File

@ -59,7 +59,14 @@ export const UserListPublicDashboardPage = () => {
<td className="max-width-10">{user.lastSeenAtAge}</td>
<td className="max-width-10">
<Stack gap={2}>
<span>{user.totalDashboards} dashboard(s)</span>
<span>
<Trans
i18nKey="public-dashboard-users-access-list.table-body.dashboard-count"
count={user.totalDashboards}
>
{{ count: user.totalDashboards }} dashboards
</Trans>
</span>
<DashboardsListModalButton email={user.email} />
</Stack>
</td>

View File

@ -19,6 +19,7 @@ import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
import { fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicker/api';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { AccessControlAction, Organization, OrgRole, Role, UserDTO, UserOrg } from 'app/types';
import { OrgRolePicker } from './OrgRolePicker';
@ -60,7 +61,9 @@ export class UserOrgs extends PureComponent<Props, State> {
const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser;
return (
<div>
<h3 className="page-heading">Organizations</h3>
<h3 className="page-heading">
<Trans i18nKey="admin.user-orgs.title">Organizations</Trans>
</h3>
<Stack gap={1.5} direction="column">
<table className="filter-table form-inline">
<tbody>
@ -80,7 +83,7 @@ export class UserOrgs extends PureComponent<Props, State> {
<div>
{canAddToOrg && (
<Button variant="secondary" onClick={this.showOrgAddModal} ref={this.addToOrgButtonRef}>
Add user to organization
<Trans i18nKey="admin.user-orgs.add-button">Add user to organization</Trans>
</Button>
)}
</div>
@ -242,7 +245,7 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps> {
onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove}
>
Remove from organization
{t('admin.user-orgs.remove-button', 'Remove from organization')}
</ConfirmButton>
)}
</td>
@ -383,10 +386,10 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
<Modal.ButtonRow>
<Stack gap={2} justifyContent="center">
<Button variant="secondary" fill="outline" onClick={this.onCancel}>
Cancel
<Trans i18nKey="admin.user-orgs-modal.cancel-button">Cancel</Trans>
</Button>
<Button variant="primary" disabled={selectedOrg === null} onClick={this.onAddUserToOrg}>
Add to organization
<Trans i18nKey="admin.user-orgs-modal.add-button">Add to organization</Trans>
</Button>
</Stack>
</Modal.ButtonRow>
@ -438,17 +441,19 @@ export function ChangeOrgButton({
interactive={true}
content={
<div>
This user&apos;s role is not editable because it is synchronized from your auth provider. Refer to
the&nbsp;
<a
className={styles.tooltipItemLink}
href={'https://grafana.com/docs/grafana/latest/auth'}
rel="noreferrer"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
<Trans i18nKey="admin.user-orgs.role-not-editable">
This user&apos;s role is not editable because it is synchronized from your auth provider. Refer to
the&nbsp;
<a
className={styles.tooltipItemLink}
href={'https://grafana.com/docs/grafana/latest/auth'}
rel="noreferrer"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
</Trans>
</div>
}
>
@ -465,7 +470,7 @@ export function ChangeOrgButton({
onConfirm={onOrgRoleSave}
disabled={isExternalUser}
>
Change role
{t('admin.user-orgs.change-role-button', 'Change role')}
</ConfirmButton>
)}
</div>
@ -486,17 +491,19 @@ export const ExternalUserTooltip = ({ lockMessage }: ExternalUserTooltipProps) =
interactive={true}
content={
<div>
This user&apos;s built-in role is not editable because it is synchronized from your auth provider. Refer to
the&nbsp;
<a
className={styles.tooltipItemLink}
href={'https://grafana.com/docs/grafana/latest/auth'}
rel="noreferrer noopener"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
<Trans i18nKey="admin.user-orgs.external-user-tooltip">
This user&apos;s built-in role is not editable because it is synchronized from your auth provider. Refer
to the&nbsp;
<a
className={styles.tooltipItemLink}
href={'https://grafana.com/docs/grafana/latest/auth'}
rel="noreferrer noopener"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
</Trans>
</div>
}
>

View File

@ -4,6 +4,7 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { ConfirmButton, RadioButtonGroup, Icon, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { ExternalUserTooltip } from 'app/features/admin/UserOrgs';
import { AccessControlAction } from 'app/types';
@ -39,11 +40,15 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
return (
<div>
<h3 className="page-heading">Permissions</h3>
<h3 className="page-heading">
<Trans i18nKey="admin.user-permissions.title">Permissions</Trans>
</h3>
<table className="filter-table form-inline">
<tbody>
<tr>
<td className="width-16">Grafana Admin</td>
<td className="width-16">
<Trans i18nKey="admin.user-permissions.grafana-admin-key">Grafana Admin</Trans>
</td>
{isEditing ? (
<td colSpan={2}>
<RadioButtonGroup
@ -57,10 +62,10 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
<td colSpan={2}>
{isGrafanaAdmin ? (
<>
<Icon name="shield" /> Yes
<Icon name="shield" /> <Trans i18nKey="admin.user-permissions.grafana-admin-yes">Yes</Trans>
</>
) : (
<>No</>
<Trans i18nKey="admin.user-permissions.grafana-admin-no">No</Trans>
)}
</td>
)}
@ -72,7 +77,7 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
onCancel={onCancelClick}
confirmText="Change"
>
Change
{t('admin.user-permissions.change-button', 'Change')}
</ConfirmButton>
)}
{isExternalUser && (

View File

@ -4,6 +4,7 @@ import * as React from 'react';
import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, Stack } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { AccessControlAction, UserDTO } from 'app/types';
interface Props {
@ -82,7 +83,9 @@ export function UserProfile({
return (
<div>
<h3 className="page-heading">User information</h3>
<h3 className="page-heading">
<Trans i18nKey="admin.user-profile.title">User information</Trans>
</h3>
<Stack direction="column" gap={1.5}>
<div>
<table className="filter-table form-inline">
@ -124,7 +127,7 @@ export function UserProfile({
{canDelete && (
<>
<Button variant="destructive" onClick={showDeleteUserModal(true)} ref={deleteUserRef}>
Delete user
<Trans i18nKey="admin.user-profile.delete-button">Delete user</Trans>
</Button>
<ConfirmModal
isOpen={showDeleteModal}
@ -138,13 +141,13 @@ export function UserProfile({
)}
{user.isDisabled && canEnable && (
<Button variant="secondary" onClick={handleUserEnable}>
Enable user
<Trans i18nKey="admin.user-profile.enable-button">Enable user</Trans>
</Button>
)}
{!user.isDisabled && canDisable && (
<>
<Button variant="secondary" onClick={showDisableUserModal(true)} ref={disableUserRef}>
Disable user
<Trans i18nKey="admin.user-profile.disable-button">Disable user</Trans>
</Button>
<ConfirmModal
isOpen={showDisableModal}
@ -282,7 +285,7 @@ export class UserProfileRow extends PureComponent<UserProfileRowProps, UserProfi
onConfirm={this.onSave}
onCancel={this.onCancelClick}
>
Edit
{t('admin.user-profile.edit-button', 'Edit')}
</ConfirmButton>
</td>
</tr>

View File

@ -3,7 +3,7 @@ import { createRef, PureComponent } from 'react';
import { ConfirmButton, ConfirmModal, Button, Stack } from '@grafana/ui';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { formatDate } from 'app/core/internationalization/dates';
import { AccessControlAction, UserSession } from 'app/types';
@ -53,16 +53,26 @@ class BaseUserSessions extends PureComponent<Props, State> {
return (
<div>
<h3 className="page-heading">Sessions</h3>
<h3 className="page-heading">
<Trans i18nKey="admin.user-sessions.title">Sessions</Trans>
</h3>
<Stack direction="column" gap={1.5}>
<div>
<table className="filter-table form-inline">
<thead>
<tr>
<th>Last seen</th>
<th>Logged on</th>
<th>IP address</th>
<th>Browser and OS</th>
<th>
<Trans i18nKey="admin.user-sessions.last-seen-column">Last seen</Trans>
</th>
<th>
<Trans i18nKey="admin.user-sessions.logged-on-column">Logged on</Trans>
</th>
<th>
<Trans i18nKey="admin.user-sessions.ip-column">IP address</Trans>
</th>
<th>
<Trans i18nKey="admin.user-sessions.browser-column">Browser and OS</Trans>
</th>
<th colSpan={2}>
<Trans i18nKey="user-session.auth-module-column">Identity Provider</Trans>
</th>
@ -86,7 +96,7 @@ class BaseUserSessions extends PureComponent<Props, State> {
confirmVariant="destructive"
onConfirm={this.onSessionRevoke(session.id)}
>
Force logout
{t('admin.user-sessions.force-logout-button', 'Force logout')}
</ConfirmButton>
)}
</td>
@ -99,7 +109,7 @@ class BaseUserSessions extends PureComponent<Props, State> {
<div>
{canLogout && sessions.length > 0 && (
<Button variant="secondary" onClick={this.showLogoutConfirmationModal} ref={this.forceAllLogoutButton}>
Force logout from all devices
<Trans i18nKey="admin.user-sessions.force-logout-all-button">Force logout from all devices</Trans>
</Button>
)}
<ConfirmModal

View File

@ -11,6 +11,7 @@ import {
Pagination,
FetchDataFunc,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { EmptyArea } from 'app/features/alerting/unified/components/EmptyArea';
import { UserAnonymousDeviceDTO } from 'app/types';
@ -117,7 +118,9 @@ export const AnonUsersDevicesTable = ({
)}
{devices.length === 0 && (
<EmptyArea>
<span>No anonymous users found.</span>
<span>
<Trans i18nKey="admin.anon-users.not-found">No anonymous users found.</Trans>
</span>
</EmptyArea>
)}
</Stack>

View File

@ -24,6 +24,7 @@ import { fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicke
import { RolePickerBadges } from 'app/core/components/RolePickerDrawer/RolePickerBadges';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { AccessControlAction, OrgUser, Role } from 'app/types';
import { OrgRolePicker } from '../OrgRolePicker';
@ -113,7 +114,21 @@ export const OrgUsersTable = ({
id: 'lastSeenAtAge',
header: 'Last active',
cell: ({ cell: { value } }: Cell<'lastSeenAtAge'>) => {
return <>{value && <>{value === '10 years' ? <Text color={'disabled'}>Never</Text> : value}</>}</>;
return (
<>
{value && (
<>
{value === '10 years' ? (
<Text color={'disabled'}>
<Trans i18nKey="admin.org-uers.last-seen-never">Never</Trans>
</Text>
) : (
value
)}
</>
)}
</>
);
},
sortType: (a, b) => new Date(a.original.lastSeenAt).getTime() - new Date(b.original.lastSeenAt).getTime(),
},
@ -170,18 +185,20 @@ export const OrgUsersTable = ({
interactive={true}
content={
<div>
This user&apos;s role is not editable because it is synchronized from your auth provider. Refer to
the&nbsp;
<a
href={
'https://grafana.com/docs/grafana/latest/administration/user-management/manage-org-users/#change-a-users-organization-permissions'
}
rel="noreferrer"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
<Trans i18nKey="admin.org-users.not-editable">
This user&apos;s role is not editable because it is synchronized from your auth provider. Refer
to the&nbsp;
<a
href={
'https://grafana.com/docs/grafana/latest/administration/user-management/manage-org-users/#change-a-users-organization-permissions'
}
rel="noreferrer"
target="_blank"
>
Grafana authentication docs
</a>
&nbsp;for details.
</Trans>
</div>
}
>

View File

@ -16,6 +16,7 @@ import {
Tooltip,
} from '@grafana/ui';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { Trans } from 'app/core/internationalization';
import { UserDTO } from 'app/types';
import { OrgUnits } from './OrgUnits';
@ -101,10 +102,12 @@ export const UsersTable = ({
cell: ({ cell: { value } }: Cell<'licensedRole'>) => {
return value === 'None' ? (
<Text color={'disabled'}>
Not assigned{' '}
<Tooltip placement="top" content="A licensed role will be assigned when this user signs in">
<Icon name="question-circle" />
</Tooltip>
<Trans i18nKey="admin.users-table.no-licensed-role">
Not assigned{' '}
<Tooltip placement="top" content="A licensed role will be assigned when this user signs in">
<Icon name="question-circle" />
</Tooltip>
</Trans>
</Text>
) : (
value
@ -121,7 +124,21 @@ export const UsersTable = ({
iconName: 'question-circle',
},
cell: ({ cell: { value } }: Cell<'lastSeenAtAge'>) => {
return <>{value && <>{value === '10 years' ? <Text color={'disabled'}>Never</Text> : value}</>}</>;
return (
<>
{value && (
<>
{value === '10 years' ? (
<Text color={'disabled'}>
<Trans i18nKey="admin.users-table.last-seen-never">Never</Trans>
</Text>
) : (
value
)}
</>
)}
</>
);
},
sortType: (a, b) => new Date(a.original.lastSeenAt!).getTime() - new Date(b.original.lastSeenAt!).getTime(),
},

View File

@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Alert, CellProps, Column, Icon, InteractiveTable, Stack, Text, Tooltip } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { AppNotificationSeverity, LdapConnectionInfo, LdapServerInfo } from 'app/types';
interface Props {
@ -54,7 +55,7 @@ export const LdapConnectionStatus = ({ ldapConnectionInfo }: Props) => {
<section>
<Stack direction="column" gap={2}>
<Text color="primary" element="h3">
LDAP Connection
<Trans i18nKey="admin.ldap-status.title">LDAP Connection</Trans>
</Text>
<InteractiveTable data={data} columns={columns} getRowId={(serverInfo) => serverInfo.host + serverInfo.port} />
<LdapErrorBox ldapConnectionInfo={ldapConnectionInfo} />
@ -83,7 +84,7 @@ export const LdapErrorBox = ({ ldapConnectionInfo }: LdapConnectionErrorProps) =
const errorElements = connectionErrors.map((info, index) => (
<div key={index}>
<span style={{ fontWeight: 500 }}>
{info.host}:{info.port}
{`${info.host}:${info.port}`}
<br />
</span>
<span>{info.error}</span>

View File

@ -7,6 +7,7 @@ import { featureEnabled } from '@grafana/runtime';
import { Alert, Button, Field, Input, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import {
AppNotificationSeverity,
@ -118,7 +119,9 @@ export const LdapPage = ({
{canReadLDAPUser && (
<section>
<h3>Test user mapping</h3>
<h3>
<Trans i18nKey="admin.ldap.test-mapping-heading">Test user mapping</Trans>
</h3>
<form onSubmit={handleSubmit(search)}>
<Field label="Username">
<Input
@ -129,7 +132,7 @@ export const LdapPage = ({
defaultValue={queryParams.username}
addonAfter={
<Button variant="primary" type="submit">
Run
<Trans i18nKey="admin.ldap.test-mapping-run-button">Run</Trans>
</Button>
}
/>

View File

@ -1,5 +1,6 @@
import { dateTimeFormat } from '@grafana/data';
import { InteractiveTable, Text } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { SyncInfo } from 'app/types';
interface Props {
@ -29,7 +30,9 @@ export const LdapSyncInfo = ({ ldapSyncInfo }: Props) => {
return (
<section>
<Text element="h3">LDAP Synchronization</Text>
<Text element="h3">
<Trans i18nKey="admin.ldap-sync-info.title">LDAP Synchronization</Trans>
</Text>
<InteractiveTable data={data} columns={columns} getRowId={(sync) => sync.syncAttribute} />
</section>
);

View File

@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Tooltip, Icon, InteractiveTable, type CellProps, Column } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { LdapRole } from 'app/types';
interface Props {
@ -28,10 +29,12 @@ export const LdapUserGroups = ({ groups }: Props) => {
cell: (props: CellProps<LdapRole, string | undefined>) =>
props.value || (
<>
No match{' '}
<Tooltip content="No matching organizations found">
<Icon name="info-circle" />
</Tooltip>
<Trans i18nKey="admin.ldap-user-groups.no-org-found">
No match{' '}
<Tooltip content="No matching organizations found">
<Icon name="info-circle" />
</Tooltip>
</Trans>
</>
),
},

View File

@ -1,4 +1,5 @@
import { Box, Stack, Text } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { LdapUser } from 'app/types';
import { LdapUserGroups } from './LdapUserGroups';
@ -21,7 +22,9 @@ export const LdapUserInfo = ({ ldapUser }: Props) => {
<LdapUserTeams teams={ldapUser.teams} />
) : (
<Box>
<Text>No teams found via LDAP</Text>
<Text>
<Trans i18nKey="admin.ldap-user-info.no-team">No teams found via LDAP</Trans>
</Text>
</Box>
)}
</Stack>

View File

@ -2,6 +2,7 @@ import { useMemo } from 'react';
import * as React from 'react';
import { Column, Icon, InteractiveTable } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { LdapPermissions } from 'app/types';
interface Props {
@ -33,9 +34,9 @@ export const LdapUserPermissions = ({ permissions }: Props) => {
{
permission: 'Grafana admin',
value: permissions.isGrafanaAdmin ? (
<>
<Trans i18nKey="admin.ldap-permissions.admin">
<Icon name="shield" /> Yes
</>
</Trans>
) : (
'No'
),
@ -43,13 +44,13 @@ export const LdapUserPermissions = ({ permissions }: Props) => {
{
permission: 'Status',
value: permissions.isDisabled ? (
<>
<Trans i18nKey="admin.ldap-permissions.inactive">
<Icon name="times" /> Inactive
</>
</Trans>
) : (
<>
<Trans i18nKey="admin.ldap-permissions.active">
<Icon name="check" /> Active
</>
</Trans>
),
},
],

View File

@ -14,6 +14,9 @@
"permission-list": {
"permission": "Permission"
},
"permission-list-item": {
"inherited": "Inherited from folder"
},
"permissions": {
"add-label": "Add a permission",
"no-permissions": "There are no permissions",
@ -25,6 +28,143 @@
"user": "User"
}
},
"action-editor": {
"modal": {
"cancel-button": "Cancel",
"save-button": "Save"
}
},
"actions-editor": {
"inline": {
"add-button": "Add action",
"one-click-link": "One-click link"
}
},
"admin": {
"anon-users": {
"not-found": "No anonymous users found."
},
"edit-org": {
"access-denied": "You do not have permission to see users in this organization. To update this organization, contact your server administrator.",
"heading": "Edit Organization",
"update-button": "Update",
"users-heading": "Organization users"
},
"feature-toggles": {
"sub-title": "View and edit feature toggles. Read more about feature toggles at <2>grafana.com</2>."
},
"get-enterprise": {
"contact-us": "Contact us and get a free trial",
"description": "You can use the trial version for free for 30 days. We will remind you about it five days before the trial period ends.",
"features-heading": "Enhanced functionality",
"included-description": "Indemnification, working with Grafana Labs on future prioritization, and training from the core Grafana team.",
"included-heading": "Also included:",
"service-title": "At your service",
"team-sync-details": "LDAP, GitHub OAuth, Auth Proxy, Okta",
"title": "Get Grafana Enterprise"
},
"ldap": {
"test-mapping-heading": "Test user mapping",
"test-mapping-run-button": "Run"
},
"ldap-permissions": {
"active": "<0></0> Active",
"admin": "<0></0> Yes",
"inactive": "<0></0> Inactive"
},
"ldap-status": {
"title": "LDAP Connection"
},
"ldap-sync": {
"debug-button": "Debug LDAP Mapping",
"external-sync-description": "User synced via LDAP. Some changes must be done in LDAP or mappings.",
"external-sync-label": "External sync",
"next-sync-label": "Next scheduled synchronization",
"not-enabled": "Not enabled",
"sync-button": "Sync user",
"title": "LDAP Synchronisation"
},
"ldap-sync-info": {
"title": "LDAP Synchronization"
},
"ldap-user-groups": {
"no-org-found": "No match <2><0></0></2>"
},
"ldap-user-info": {
"no-team": "No teams found via LDAP"
},
"org-uers": {
"last-seen-never": "Never"
},
"org-users": {
"not-editable": "This user's role is not editable because it is synchronized from your auth provider. Refer to the <1>Grafana authentication docs</1> for details."
},
"orgs": {
"delete-body": "Are you sure you want to delete '{{deleteOrgName}}'?<3></3> <5>All dashboards for this organization will be removed!</5>",
"id-header": "ID",
"name-header": "Name",
"new-org-button": "New org"
},
"server-settings": {
"alerts-button": "Manage alerts",
"dashboards-button": "Manage dashboards",
"data-sources-button": "Manage data sources",
"not-found": "No stats found.",
"title": "Instance statistics",
"users-button": "Manage users"
},
"settings": {
"info-description": "These system settings are defined in grafana.ini or custom.ini (or overridden in ENV variables). To change these you currently need to restart Grafana."
},
"upgrade-info": {
"title": "Enterprise license"
},
"user-orgs": {
"add-button": "Add user to organization",
"change-role-button": "Change role",
"external-user-tooltip": "This user's built-in role is not editable because it is synchronized from your auth provider. Refer to the <1>Grafana authentication docs</1> for details.",
"remove-button": "Remove from organization",
"role-not-editable": "This user's role is not editable because it is synchronized from your auth provider. Refer to the <1>Grafana authentication docs</1> for details.",
"title": "Organizations"
},
"user-orgs-modal": {
"add-button": "Add to organization",
"cancel-button": "Cancel"
},
"user-permissions": {
"change-button": "Change",
"grafana-admin-key": "Grafana Admin",
"grafana-admin-no": "No",
"grafana-admin-yes": "Yes",
"title": "Permissions"
},
"user-profile": {
"delete-button": "Delete user",
"disable-button": "Disable user",
"edit-button": "Edit",
"enable-button": "Enable user",
"title": "User information"
},
"user-sessions": {
"browser-column": "Browser and OS",
"force-logout-all-button": "Force logout from all devices",
"force-logout-button": "Force logout",
"ip-column": "IP address",
"last-seen-column": "Last seen",
"logged-on-column": "Logged on",
"title": "Sessions"
},
"users-create": {
"create-button": "Create user"
},
"users-list": {
"create-button": "New user"
},
"users-table": {
"last-seen-never": "Never",
"no-licensed-role": "Not assigned <2><0></0></2>"
}
},
"alert-labels": {
"button": {
"hide": "Hide common labels",
@ -371,6 +511,17 @@
"message": "No API keys found"
}
},
"app-chrome": {
"skip-content-button": "Skip to main content",
"top-bar": {
"sign-in": "Sign in"
}
},
"app-notification": {
"item": {
"trace-id": "Trace ID: {{traceId}}"
}
},
"bookmarks-page": {
"empty": {
"message": "It looks like you havent created any bookmarks yet",
@ -406,15 +557,15 @@
},
"counts": {
"alertRule_one": "{{count}} alert rule",
"alertRule_other": "{{count}} alert rule",
"alertRule_other": "{{count}} alert rules",
"dashboard_one": "{{count}} dashboard",
"dashboard_other": "{{count}} dashboard",
"dashboard_other": "{{count}} dashboards",
"folder_one": "{{count}} folder",
"folder_other": "{{count}} folder",
"folder_other": "{{count}} folders",
"libraryPanel_one": "{{count}} library panel",
"libraryPanel_other": "{{count}} library panel",
"libraryPanel_other": "{{count}} library panels",
"total_one": "{{count}} item",
"total_other": "{{count}} item"
"total_other": "{{count}} items"
},
"dashboards-tree": {
"collapse-folder-button": "Collapse folder {{title}}",
@ -442,6 +593,7 @@
"clear-selection": "Clear selection",
"empty-message": "No folders found",
"error-title": "Error loading folders",
"non-folder-item": "Non-folder {{itemKind}} {{itemUID}}",
"search-placeholder": "Search folders",
"unknown-error": "Unknown error"
},
@ -883,6 +1035,12 @@
"time-range-label": "Lock time range"
}
},
"empty-list-cta": {
"pro-tip": "ProTip: {{proTip}}"
},
"entity-not-found": {
"description": "We're looking but can't seem to find this {{lowerCaseEntity}}. Try returning <4>home</4> or seeking help on the <7>community site.</7>"
},
"errors": {
"dashboard-settings": {
"annotations": {
@ -1103,9 +1261,29 @@
"export-as-json-tooltip": "Export"
}
},
"folder-filter": {
"clear-folder-button": "Clear folders"
},
"folder-picker": {
"create-instructions": "Press enter to create the new folder.",
"loading": "Loading folders..."
},
"forgot-password": {
"back-button": "Back to login",
"change-password": {
"skip-button": "Skip",
"submit-button": "Submit"
},
"contact-admin": "Did you forget your username or email? Contact your Grafana administrator.",
"email-sent": "An email with a reset link has been sent to the email address. You should receive it shortly.",
"reset-password-header": "Reset password",
"send-email-button": "Send reset email"
},
"form-prompt": {
"continue-button": "Continue editing",
"description": "Changes that you made may not be saved.",
"discard-button": "Discard unsaved changes"
},
"gen-ai": {
"apply-suggestion": "Apply",
"incomplete-request-error": "Sorry, I was unable to complete your request. Please try again.",
@ -1225,6 +1403,10 @@
}
},
"help-modal": {
"column-headers": {
"description": "Description",
"keys": "Keys"
},
"shortcuts-category": {
"dashboard": "Dashboard",
"focused-panel": "Focused panel",
@ -1475,6 +1657,9 @@
}
},
"login": {
"divider": {
"connecting-text": "or"
},
"error": {
"blocked": "You have exceeded the number of login attempts for this user. Please try again later.",
"invalid-user-or-password": "Invalid username or password",
@ -1502,6 +1687,9 @@
"verify-email-label": "Send a verification email",
"verify-email-loading-label": "Sending email..."
},
"layout": {
"update-password": "Update your password"
},
"services": {
"sing-in-with-prefix": "Sign in with {{serviceName}}"
},
@ -2147,6 +2335,9 @@
"no-matches": "No matches found",
"unsupported-layout": "Unsupported layout"
},
"panel-type-filter": {
"clear-button": "Clear types"
},
"playlist-edit": {
"error-prefix": "Error loading playlist:",
"form": {
@ -2240,6 +2431,10 @@
},
"empty-state": {
"message": "No plugins found"
},
"plugin-help": {
"error": "An error occurred when loading help.",
"not-found": "No query help could be found."
}
},
"profile": {
@ -2433,6 +2628,10 @@
"dashboard-modal-title": "Public dashboards",
"shared-dashboard-modal-title": "Shared dashboards"
},
"table-body": {
"dashboard-count_one": "{{count}} dashboard",
"dashboard-count_other": "{{count}} dashboards"
},
"table-header": {
"activated-label": "Activated",
"activated-tooltip": "Earliest time user has been an active user to a dashboard",
@ -2527,6 +2726,19 @@
"dismissable-button": "Close"
},
"role-picker": {
"built-in": {
"basic-roles": "Basic roles"
},
"input": {
"no-roles": "No roles assigned"
},
"menu": {
"clear-button": "Clear all",
"tooltip": "You can now select the \"No basic role\" option and add permissions to your custom needs. You can find more information in <1>our documentation</1>."
},
"sub-menu": {
"clear-button": "Clear"
},
"title": {
"description": "Assign roles to users to ensure granular control over access to Grafana&lsquo;s features and resources. Find out more in our <2>documentation</2>."
}
@ -2536,6 +2748,11 @@
"label": "Basic Roles"
}
},
"route-error": {
"description": "Grafana has likely been updated. Please try reloading the page.",
"reload-button": "Reload",
"title": "Unable to find application file"
},
"save-dashboards": {
"name-exists": {
"message-info": "A dashboard with the same name in the selected folder already exists, including recently deleted dashboards.",
@ -2808,6 +3025,17 @@
},
"title": "Preferences"
},
"sign-up": {
"back-button": "Back to login",
"submit-button": "Submit",
"verify": {
"back-button": "Back to login",
"complete-button": "Complete signup",
"header": "Verify email",
"info": "An email with a verification link has been sent to the email address. You should receive it shortly.",
"send-button": "Send verification email"
}
},
"silences": {
"empty-state": {
"button-title": "Create silence",
@ -2860,6 +3088,7 @@
"view-button": "View"
},
"tag-filter": {
"clear-button": "Clear tags",
"loading": "Loading...",
"no-tags": "No tags found",
"placeholder": "Filter by tag"
@ -2980,6 +3209,13 @@
"add-transformation-header": "Start transforming data"
}
},
"upgrade-box": {
"discovery-text": "Youve discovered a Pro feature!",
"discovery-text-continued": "Get the Grafana Pro plan to access {{featureName}}.",
"get-started": "Get started with {{featureName}}",
"learn-more": "Learn more",
"upgrade-button": "Upgrade"
},
"user-orgs": {
"current-org-button": "Current",
"name-column": "Name",

View File

@ -14,6 +14,9 @@
"permission-list": {
"permission": "Pęřmįşşįőʼn"
},
"permission-list-item": {
"inherited": "Ĩʼnĥęřįŧęđ ƒřőm ƒőľđęř"
},
"permissions": {
"add-label": "Åđđ ä pęřmįşşįőʼn",
"no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş",
@ -25,6 +28,143 @@
"user": "Ůşęř"
}
},
"action-editor": {
"modal": {
"cancel-button": "Cäʼnčęľ",
"save-button": "Ŝävę"
}
},
"actions-editor": {
"inline": {
"add-button": "Åđđ äčŧįőʼn",
"one-click-link": "Øʼnę-čľįčĸ ľįʼnĸ"
}
},
"admin": {
"anon-users": {
"not-found": "Ńő äʼnőʼnymőūş ūşęřş ƒőūʼnđ."
},
"edit-org": {
"access-denied": "Ÿőū đő ʼnőŧ ĥävę pęřmįşşįőʼn ŧő şęę ūşęřş įʼn ŧĥįş őřģäʼnįžäŧįőʼn. Ŧő ūpđäŧę ŧĥįş őřģäʼnįžäŧįőʼn, čőʼnŧäčŧ yőūř şęřvęř äđmįʼnįşŧřäŧőř.",
"heading": "Ēđįŧ Øřģäʼnįžäŧįőʼn",
"update-button": "Ůpđäŧę",
"users-heading": "Øřģäʼnįžäŧįőʼn ūşęřş"
},
"feature-toggles": {
"sub-title": "Vįęŵ äʼnđ ęđįŧ ƒęäŧūřę ŧőģģľęş. Ŗęäđ mőřę äþőūŧ ƒęäŧūřę ŧőģģľęş äŧ <2>ģřäƒäʼnä.čőm</2>."
},
"get-enterprise": {
"contact-us": "Cőʼnŧäčŧ ūş äʼnđ ģęŧ ä ƒřęę ŧřįäľ",
"description": "Ÿőū čäʼn ūşę ŧĥę ŧřįäľ vęřşįőʼn ƒőř ƒřęę ƒőř 30 đäyş. Ŵę ŵįľľ řęmįʼnđ yőū äþőūŧ įŧ ƒįvę đäyş þęƒőřę ŧĥę ŧřįäľ pęřįőđ ęʼnđş.",
"features-heading": "Ēʼnĥäʼnčęđ ƒūʼnčŧįőʼnäľįŧy",
"included-description": "Ĩʼnđęmʼnįƒįčäŧįőʼn, ŵőřĸįʼnģ ŵįŧĥ Ğřäƒäʼnä Ŀäþş őʼn ƒūŧūřę přįőřįŧįžäŧįőʼn, äʼnđ ŧřäįʼnįʼnģ ƒřőm ŧĥę čőřę Ğřäƒäʼnä ŧęäm.",
"included-heading": "Åľşő įʼnčľūđęđ:",
"service-title": "Åŧ yőūř şęřvįčę",
"team-sync-details": "ĿĐÅP, ĞįŧĦūþ ØÅūŧĥ, Åūŧĥ Přőχy, Øĸŧä",
"title": "Ğęŧ Ğřäƒäʼnä Ēʼnŧęřpřįşę"
},
"ldap": {
"test-mapping-heading": "Ŧęşŧ ūşęř mäppįʼnģ",
"test-mapping-run-button": "Ŗūʼn"
},
"ldap-permissions": {
"active": "<0></0> Åčŧįvę",
"admin": "<0></0> Ÿęş",
"inactive": "<0></0> Ĩʼnäčŧįvę"
},
"ldap-status": {
"title": "ĿĐÅP Cőʼnʼnęčŧįőʼn"
},
"ldap-sync": {
"debug-button": "Đęþūģ ĿĐÅP Mäppįʼnģ",
"external-sync-description": "Ůşęř şyʼnčęđ vįä ĿĐÅP. Ŝőmę čĥäʼnģęş mūşŧ þę đőʼnę įʼn ĿĐÅP őř mäppįʼnģş.",
"external-sync-label": "Ēχŧęřʼnäľ şyʼnč",
"next-sync-label": "Ńęχŧ şčĥęđūľęđ şyʼnčĥřőʼnįžäŧįőʼn",
"not-enabled": "Ńőŧ ęʼnäþľęđ",
"sync-button": "Ŝyʼnč ūşęř",
"title": "ĿĐÅP Ŝyʼnčĥřőʼnįşäŧįőʼn"
},
"ldap-sync-info": {
"title": "ĿĐÅP Ŝyʼnčĥřőʼnįžäŧįőʼn"
},
"ldap-user-groups": {
"no-org-found": "Ńő mäŧčĥ <2><0></0></2>"
},
"ldap-user-info": {
"no-team": "Ńő ŧęämş ƒőūʼnđ vįä ĿĐÅP"
},
"org-uers": {
"last-seen-never": "Ńęvęř"
},
"org-users": {
"not-editable": "Ŧĥįş ūşęř'ş řőľę įş ʼnőŧ ęđįŧäþľę þęčäūşę įŧ įş şyʼnčĥřőʼnįžęđ ƒřőm yőūř äūŧĥ přővįđęř. Ŗęƒęř ŧő ŧĥę <1>Ğřäƒäʼnä äūŧĥęʼnŧįčäŧįőʼn đőčş</1> ƒőř đęŧäįľş."
},
"orgs": {
"delete-body": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő đęľęŧę '{{deleteOrgName}}'?<3></3> <5>Åľľ đäşĥþőäřđş ƒőř ŧĥįş őřģäʼnįžäŧįőʼn ŵįľľ þę řęmővęđ!</5>",
"id-header": "ĨĐ",
"name-header": "Ńämę",
"new-org-button": "Ńęŵ őřģ"
},
"server-settings": {
"alerts-button": "Mäʼnäģę äľęřŧş",
"dashboards-button": "Mäʼnäģę đäşĥþőäřđş",
"data-sources-button": "Mäʼnäģę đäŧä şőūřčęş",
"not-found": "Ńő şŧäŧş ƒőūʼnđ.",
"title": "Ĩʼnşŧäʼnčę şŧäŧįşŧįčş",
"users-button": "Mäʼnäģę ūşęřş"
},
"settings": {
"info-description": "Ŧĥęşę şyşŧęm şęŧŧįʼnģş äřę đęƒįʼnęđ įʼn ģřäƒäʼnä.įʼnį őř čūşŧőm.įʼnį (őř ővęřřįđđęʼn įʼn ĒŃV väřįäþľęş). Ŧő čĥäʼnģę ŧĥęşę yőū čūřřęʼnŧľy ʼnęęđ ŧő řęşŧäřŧ Ğřäƒäʼnä."
},
"upgrade-info": {
"title": "Ēʼnŧęřpřįşę ľįčęʼnşę"
},
"user-orgs": {
"add-button": "Åđđ ūşęř ŧő őřģäʼnįžäŧįőʼn",
"change-role-button": "Cĥäʼnģę řőľę",
"external-user-tooltip": "Ŧĥįş ūşęř'ş þūįľŧ-įʼn řőľę įş ʼnőŧ ęđįŧäþľę þęčäūşę įŧ įş şyʼnčĥřőʼnįžęđ ƒřőm yőūř äūŧĥ přővįđęř. Ŗęƒęř ŧő ŧĥę <1>Ğřäƒäʼnä äūŧĥęʼnŧįčäŧįőʼn đőčş</1> ƒőř đęŧäįľş.",
"remove-button": "Ŗęmővę ƒřőm őřģäʼnįžäŧįőʼn",
"role-not-editable": "Ŧĥįş ūşęř'ş řőľę įş ʼnőŧ ęđįŧäþľę þęčäūşę įŧ įş şyʼnčĥřőʼnįžęđ ƒřőm yőūř äūŧĥ přővįđęř. Ŗęƒęř ŧő ŧĥę <1>Ğřäƒäʼnä äūŧĥęʼnŧįčäŧįőʼn đőčş</1> ƒőř đęŧäįľş.",
"title": "Øřģäʼnįžäŧįőʼnş"
},
"user-orgs-modal": {
"add-button": "Åđđ ŧő őřģäʼnįžäŧįőʼn",
"cancel-button": "Cäʼnčęľ"
},
"user-permissions": {
"change-button": "Cĥäʼnģę",
"grafana-admin-key": "Ğřäƒäʼnä Åđmįʼn",
"grafana-admin-no": "Ńő",
"grafana-admin-yes": "Ÿęş",
"title": "Pęřmįşşįőʼnş"
},
"user-profile": {
"delete-button": "Đęľęŧę ūşęř",
"disable-button": "Đįşäþľę ūşęř",
"edit-button": "Ēđįŧ",
"enable-button": "Ēʼnäþľę ūşęř",
"title": "Ůşęř įʼnƒőřmäŧįőʼn"
},
"user-sessions": {
"browser-column": "ßřőŵşęř äʼnđ ØŜ",
"force-logout-all-button": "Főřčę ľőģőūŧ ƒřőm äľľ đęvįčęş",
"force-logout-button": "Főřčę ľőģőūŧ",
"ip-column": "ĨP äđđřęşş",
"last-seen-column": "Ŀäşŧ şęęʼn",
"logged-on-column": "Ŀőģģęđ őʼn",
"title": "Ŝęşşįőʼnş"
},
"users-create": {
"create-button": "Cřęäŧę ūşęř"
},
"users-list": {
"create-button": "Ńęŵ ūşęř"
},
"users-table": {
"last-seen-never": "Ńęvęř",
"no-licensed-role": "Ńőŧ äşşįģʼnęđ <2><0></0></2>"
}
},
"alert-labels": {
"button": {
"hide": "Ħįđę čőmmőʼn ľäþęľş",
@ -371,6 +511,17 @@
"message": "Ńő ÅPĨ ĸęyş ƒőūʼnđ"
}
},
"app-chrome": {
"skip-content-button": "Ŝĸįp ŧő mäįʼn čőʼnŧęʼnŧ",
"top-bar": {
"sign-in": "Ŝįģʼn įʼn"
}
},
"app-notification": {
"item": {
"trace-id": "Ŧřäčę ĨĐ: {{traceId}}"
}
},
"bookmarks-page": {
"empty": {
"message": "Ĩŧ ľőőĸş ľįĸę yőū ĥävęʼnŧ čřęäŧęđ äʼny þőőĸmäřĸş yęŧ",
@ -406,15 +557,15 @@
},
"counts": {
"alertRule_one": "{{count}} äľęřŧ řūľę",
"alertRule_other": "{{count}} äľęřŧ řūľę",
"alertRule_other": "{{count}} äľęřŧ řūľęş",
"dashboard_one": "{{count}} đäşĥþőäřđ",
"dashboard_other": "{{count}} đäşĥþőäřđ",
"dashboard_other": "{{count}} đäşĥþőäřđş",
"folder_one": "{{count}} ƒőľđęř",
"folder_other": "{{count}} ƒőľđęř",
"folder_other": "{{count}} ƒőľđęřş",
"libraryPanel_one": "{{count}} ľįþřäřy päʼnęľ",
"libraryPanel_other": "{{count}} ľįþřäřy päʼnęľ",
"libraryPanel_other": "{{count}} ľįþřäřy päʼnęľş",
"total_one": "{{count}} įŧęm",
"total_other": "{{count}} įŧęm"
"total_other": "{{count}} įŧęmş"
},
"dashboards-tree": {
"collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}",
@ -442,6 +593,7 @@
"clear-selection": "Cľęäř şęľęčŧįőʼn",
"empty-message": "Ńő ƒőľđęřş ƒőūʼnđ",
"error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş",
"non-folder-item": "Ńőʼn-ƒőľđęř {{itemKind}} {{itemUID}}",
"search-placeholder": "Ŝęäřčĥ ƒőľđęřş",
"unknown-error": "Ůʼnĸʼnőŵʼn ęřřőř"
},
@ -883,6 +1035,12 @@
"time-range-label": "Ŀőčĸ ŧįmę řäʼnģę"
}
},
"empty-list-cta": {
"pro-tip": "PřőŦįp: {{proTip}}"
},
"entity-not-found": {
"description": "Ŵę'řę ľőőĸįʼnģ þūŧ čäʼn'ŧ şęęm ŧő ƒįʼnđ ŧĥįş {{lowerCaseEntity}}. Ŧřy řęŧūřʼnįʼnģ <4>ĥőmę</4> őř şęęĸįʼnģ ĥęľp őʼn ŧĥę <7>čőmmūʼnįŧy şįŧę.</7>"
},
"errors": {
"dashboard-settings": {
"annotations": {
@ -1103,9 +1261,29 @@
"export-as-json-tooltip": "Ēχpőřŧ"
}
},
"folder-filter": {
"clear-folder-button": "Cľęäř ƒőľđęřş"
},
"folder-picker": {
"create-instructions": "Přęşş ęʼnŧęř ŧő čřęäŧę ŧĥę ʼnęŵ ƒőľđęř.",
"loading": "Ŀőäđįʼnģ ƒőľđęřş..."
},
"forgot-password": {
"back-button": "ßäčĸ ŧő ľőģįʼn",
"change-password": {
"skip-button": "Ŝĸįp",
"submit-button": "Ŝūþmįŧ"
},
"contact-admin": "Đįđ yőū ƒőřģęŧ yőūř ūşęřʼnämę őř ęmäįľ? Cőʼnŧäčŧ yőūř Ğřäƒäʼnä äđmįʼnįşŧřäŧőř.",
"email-sent": "Åʼn ęmäįľ ŵįŧĥ ä řęşęŧ ľįʼnĸ ĥäş þęęʼn şęʼnŧ ŧő ŧĥę ęmäįľ äđđřęşş. Ÿőū şĥőūľđ řęčęįvę įŧ şĥőřŧľy.",
"reset-password-header": "Ŗęşęŧ päşşŵőřđ",
"send-email-button": "Ŝęʼnđ řęşęŧ ęmäįľ"
},
"form-prompt": {
"continue-button": "Cőʼnŧįʼnūę ęđįŧįʼnģ",
"description": "Cĥäʼnģęş ŧĥäŧ yőū mäđę mäy ʼnőŧ þę şävęđ.",
"discard-button": "Đįşčäřđ ūʼnşävęđ čĥäʼnģęş"
},
"gen-ai": {
"apply-suggestion": "Åppľy",
"incomplete-request-error": "Ŝőřřy, Ĩ ŵäş ūʼnäþľę ŧő čőmpľęŧę yőūř řęqūęşŧ. Pľęäşę ŧřy äģäįʼn.",
@ -1225,6 +1403,10 @@
}
},
"help-modal": {
"column-headers": {
"description": "Đęşčřįpŧįőʼn",
"keys": "Ķęyş"
},
"shortcuts-category": {
"dashboard": "Đäşĥþőäřđ",
"focused-panel": "Főčūşęđ päʼnęľ",
@ -1475,6 +1657,9 @@
}
},
"login": {
"divider": {
"connecting-text": "őř"
},
"error": {
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
"invalid-user-or-password": "Ĩʼnväľįđ ūşęřʼnämę őř päşşŵőřđ",
@ -1502,6 +1687,9 @@
"verify-email-label": "Ŝęʼnđ ä vęřįƒįčäŧįőʼn ęmäįľ",
"verify-email-loading-label": "Ŝęʼnđįʼnģ ęmäįľ..."
},
"layout": {
"update-password": "Ůpđäŧę yőūř päşşŵőřđ"
},
"services": {
"sing-in-with-prefix": "Ŝįģʼn įʼn ŵįŧĥ {{serviceName}}"
},
@ -2147,6 +2335,9 @@
"no-matches": "Ńő mäŧčĥęş ƒőūʼnđ",
"unsupported-layout": "Ůʼnşūppőřŧęđ ľäyőūŧ"
},
"panel-type-filter": {
"clear-button": "Cľęäř ŧypęş"
},
"playlist-edit": {
"error-prefix": "Ēřřőř ľőäđįʼnģ pľäyľįşŧ:",
"form": {
@ -2240,6 +2431,10 @@
},
"empty-state": {
"message": "Ńő pľūģįʼnş ƒőūʼnđ"
},
"plugin-help": {
"error": "Åʼn ęřřőř őččūřřęđ ŵĥęʼn ľőäđįʼnģ ĥęľp.",
"not-found": "Ńő qūęřy ĥęľp čőūľđ þę ƒőūʼnđ."
}
},
"profile": {
@ -2433,6 +2628,10 @@
"dashboard-modal-title": "Pūþľįč đäşĥþőäřđş",
"shared-dashboard-modal-title": "Ŝĥäřęđ đäşĥþőäřđş"
},
"table-body": {
"dashboard-count_one": "{{count}} đäşĥþőäřđ",
"dashboard-count_other": "{{count}} đäşĥþőäřđş"
},
"table-header": {
"activated-label": "Åčŧįväŧęđ",
"activated-tooltip": "Ēäřľįęşŧ ŧįmę ūşęř ĥäş þęęʼn äʼn äčŧįvę ūşęř ŧő ä đäşĥþőäřđ",
@ -2527,6 +2726,19 @@
"dismissable-button": "Cľőşę"
},
"role-picker": {
"built-in": {
"basic-roles": "ßäşįč řőľęş"
},
"input": {
"no-roles": "Ńő řőľęş äşşįģʼnęđ"
},
"menu": {
"clear-button": "Cľęäř äľľ",
"tooltip": "Ÿőū čäʼn ʼnőŵ şęľęčŧ ŧĥę \"Ńő þäşįč řőľę\" őpŧįőʼn äʼnđ äđđ pęřmįşşįőʼnş ŧő yőūř čūşŧőm ʼnęęđş. Ÿőū čäʼn ƒįʼnđ mőřę įʼnƒőřmäŧįőʼn įʼn <1>őūř đőčūmęʼnŧäŧįőʼn</1>."
},
"sub-menu": {
"clear-button": "Cľęäř"
},
"title": {
"description": "Åşşįģʼn řőľęş ŧő ūşęřş ŧő ęʼnşūřę ģřäʼnūľäř čőʼnŧřőľ ővęř äččęşş ŧő Ğřäƒäʼnä&ľşqūő;ş ƒęäŧūřęş äʼnđ řęşőūřčęş. Fįʼnđ őūŧ mőřę įʼn őūř <2>đőčūmęʼnŧäŧįőʼn</2>."
}
@ -2536,6 +2748,11 @@
"label": "ßäşįč Ŗőľęş"
}
},
"route-error": {
"description": "Ğřäƒäʼnä ĥäş ľįĸęľy þęęʼn ūpđäŧęđ. Pľęäşę ŧřy řęľőäđįʼnģ ŧĥę päģę.",
"reload-button": "Ŗęľőäđ",
"title": "Ůʼnäþľę ŧő ƒįʼnđ äppľįčäŧįőʼn ƒįľę"
},
"save-dashboards": {
"name-exists": {
"message-info": "Å đäşĥþőäřđ ŵįŧĥ ŧĥę şämę ʼnämę įʼn ŧĥę şęľęčŧęđ ƒőľđęř äľřęäđy ęχįşŧş, įʼnčľūđįʼnģ řęčęʼnŧľy đęľęŧęđ đäşĥþőäřđş.",
@ -2808,6 +3025,17 @@
},
"title": "Přęƒęřęʼnčęş"
},
"sign-up": {
"back-button": "ßäčĸ ŧő ľőģįʼn",
"submit-button": "Ŝūþmįŧ",
"verify": {
"back-button": "ßäčĸ ŧő ľőģįʼn",
"complete-button": "Cőmpľęŧę şįģʼnūp",
"header": "Vęřįƒy ęmäįľ",
"info": "Åʼn ęmäįľ ŵįŧĥ ä vęřįƒįčäŧįőʼn ľįʼnĸ ĥäş þęęʼn şęʼnŧ ŧő ŧĥę ęmäįľ äđđřęşş. Ÿőū şĥőūľđ řęčęįvę įŧ şĥőřŧľy.",
"send-button": "Ŝęʼnđ vęřįƒįčäŧįőʼn ęmäįľ"
}
},
"silences": {
"empty-state": {
"button-title": "Cřęäŧę şįľęʼnčę",
@ -2860,6 +3088,7 @@
"view-button": "Vįęŵ"
},
"tag-filter": {
"clear-button": "Cľęäř ŧäģş",
"loading": "Ŀőäđįʼnģ...",
"no-tags": "Ńő ŧäģş ƒőūʼnđ",
"placeholder": "Fįľŧęř þy ŧäģ"
@ -2980,6 +3209,13 @@
"add-transformation-header": "Ŝŧäřŧ ŧřäʼnşƒőřmįʼnģ đäŧä"
}
},
"upgrade-box": {
"discovery-text": "Ÿőūvę đįşčővęřęđ ä Přő ƒęäŧūřę!",
"discovery-text-continued": "Ğęŧ ŧĥę Ğřäƒäʼnä Přő pľäʼn ŧő äččęşş {{featureName}}.",
"get-started": "Ğęŧ şŧäřŧęđ ŵįŧĥ {{featureName}}",
"learn-more": "Ŀęäřʼn mőřę",
"upgrade-button": "Ůpģřäđę"
},
"user-orgs": {
"current-org-button": "Cūřřęʼnŧ",
"name-column": "Ńämę",