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 (\`notifyApp\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`hideAppNotification\`)", "3"] [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": [ "public/app/core/components/AccessControl/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] [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": [ "public/app/core/components/DynamicImports/SafeDynamicImport.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [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": [ "public/app/core/components/GraphNG/GraphNG.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"], [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": [ "public/app/core/components/LocalStorageValueProvider/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./LocalStorageValueProvider\`)", "0"] [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": [ "public/app/core/components/TagFilter/TagFilter.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
], ],
"public/app/core/components/TimeSeries/utils.ts:5381": [ "public/app/core/components/TimeSeries/utils.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"] [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": [ "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 (\`config\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`Settings\`)", "1"] [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 (\`TimeSeries\`)", "6"],
[0, 0, 0, "Do not re-export imported variable (\`updateLegendValues\`)", "7"] [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": [ "public/app/core/navigation/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [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.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"] [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": [ "public/app/features/actions/ParamsEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [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": [ "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 />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"], [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 />", "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": [ "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 />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"] [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": [ "public/app/features/alerting/routes.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [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 { GrafanaTheme2 } from '@grafana/data';
import { Box, Button, Icon, Select, Tooltip, useStyles2 } from '@grafana/ui'; import { Box, Button, Icon, Select, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { ResourcePermission } from './types'; import { ResourcePermission } from './types';
@ -20,7 +21,13 @@ export const PermissionListItem = ({ item, permissionLevels, canSet, onRemove, o
<tr> <tr>
<td>{getAvatar(item)}</td> <td>{getAvatar(item)}</td>
<td>{getDescription(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> <td>
<Select <Select
disabled={!canSet || !item.isManaged} 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 { useStyles2, LinkButton, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext'; import { useGrafana } from 'app/core/context/GrafanaContext';
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
import { Trans } from 'app/core/internationalization';
import store from 'app/core/store'; import store from 'app/core/store';
import { CommandPalette } from 'app/features/commandPalette/CommandPalette'; import { CommandPalette } from 'app/features/commandPalette/CommandPalette';
import { ScopesDashboards, useScopesDashboardsState } from 'app/features/scopes'; import { ScopesDashboards, useScopesDashboardsState } from 'app/features/scopes';
@ -95,7 +96,7 @@ export function AppChrome({ children }: Props) {
{!state.chromeless && ( {!state.chromeless && (
<> <>
<LinkButton className={styles.skipLink} href="#pageContent"> <LinkButton className={styles.skipLink} href="#pageContent">
Skip to main content <Trans i18nKey="app-chrome.skip-content-button">Skip to main content</Trans>
</LinkButton> </LinkButton>
{isSingleTopNav && menuDockedAndOpen && ( {isSingleTopNav && menuDockedAndOpen && (
<MegaMenu className={styles.dockedMegaMenu} onClose={() => chrome.setMegaMenuOpen(false)} /> <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 { GrafanaTheme2, locationUtil, textUtil } from '@grafana/data';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
export function SignInLink() { export function SignInLink() {
const location = useLocation(); const location = useLocation();
@ -17,7 +18,7 @@ export function SignInLink() {
return ( return (
<a className={styles.link} href={loginUrl} target="_self"> <a className={styles.link} href={loginUrl} target="_self">
Sign in <Trans i18nKey="app-chrome.top-bar.sign-in">Sign in</Trans>
</a> </a>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { Field, Input, Button, Legend, Container, useStyles2, LinkButton, Stack } from '@grafana/ui'; import { Field, Input, Button, Legend, Container, useStyles2, LinkButton, Stack } from '@grafana/ui';
import config from 'app/core/config'; import config from 'app/core/config';
import { Trans } from 'app/core/internationalization';
interface EmailDTO { interface EmailDTO {
userOrEmail: string; userOrEmail: string;
@ -40,17 +41,23 @@ export const ForgottenPassword = () => {
if (emailSent) { if (emailSent) {
return ( return (
<div> <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" /> <Container margin="md" />
<LinkButton variant="primary" href={loginHref}> <LinkButton variant="primary" href={loginHref}>
Back to login <Trans i18nKey="forgot-password.back-button">Back to login</Trans>
</LinkButton> </LinkButton>
</div> </div>
); );
} }
return ( return (
<form onSubmit={handleSubmit(sendEmail)}> <form onSubmit={handleSubmit(sendEmail)}>
<Legend>Reset password</Legend> <Legend>
<Trans i18nKey="forgot-password.reset-password-header">Reset password</Trans>
</Legend>
<Field <Field
label="User" label="User"
description="Enter your information to get a reset link sent to you" description="Enter your information to get a reset link sent to you"
@ -64,13 +71,19 @@ export const ForgottenPassword = () => {
/> />
</Field> </Field>
<Stack> <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}> <LinkButton fill="text" href={loginHref}>
Back to login <Trans i18nKey="forgot-password.back-button">Back to login</Trans>
</LinkButton> </LinkButton>
</Stack> </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> </form>
); );
}; };

View File

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

View File

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

View File

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

View File

@ -160,9 +160,13 @@ function Row({ index, style: virtualStyles, data }: RowProps) {
} }
if (item.kind !== 'folder') { if (item.kind !== 'folder') {
const itemKind = item.kind;
const itemUID = item.uid;
return process.env.NODE_ENV !== 'production' ? ( return process.env.NODE_ENV !== 'production' ? (
<span style={virtualStyles} className={styles.row}> <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> </span>
) : null; ) : null;
} }

View File

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

View File

@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react';
import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data';
import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui'; import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { getAllPanelPluginMeta } from 'app/features/panel/state/util'; import { getAllPanelPluginMeta } from 'app/features/panel/state/util';
export interface Props { export interface Props {
@ -53,7 +54,7 @@ export const PanelTypeFilter = ({ onChange: propsOnChange, maxMenuHeight }: Prop
onClick={() => onChange([])} onClick={() => onChange([])}
aria-label="Clear types" aria-label="Clear types"
> >
Clear types <Trans i18nKey="panel-type-filter.clear-button">Clear types</Trans>
</Button> </Button>
)} )}
<MultiSelect<PanelPluginMeta> {...selectOptions} prefix={<Icon name="filter" />} aria-label="Panel Type filter" /> <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 { renderMarkdown } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { LoadingPlaceholder } from '@grafana/ui'; import { LoadingPlaceholder } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
interface Props { interface Props {
pluginId: string; pluginId: string;
@ -20,11 +21,19 @@ export function PluginHelp({ pluginId }: Props) {
} }
if (error) { 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 === '') { 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 }} />; return <div className="markdown-html" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />;

View File

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { Icon, RadioButtonList, Tooltip, useStyles2, useTheme2, PopoverContent } from '@grafana/ui'; import { Icon, RadioButtonList, Tooltip, useStyles2, useTheme2, PopoverContent } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { OrgRole } from 'app/types'; import { OrgRole } from 'app/types';
import { getStyles } from './styles'; import { getStyles } from './styles';
@ -24,7 +25,9 @@ export const BuiltinRoleSelector = ({ value, onChange, disabled, disabledMesssag
return ( return (
<> <>
<div className={styles.groupHeader}> <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 && ( {disabled && disabledMesssage && (
<Tooltip placement="right-end" interactive={true} content={<div>{disabledMesssage}</div>}> <Tooltip placement="right-end" interactive={true} content={<div>{disabledMesssage}</div>}>
<Icon name="question-circle" /> <Icon name="question-circle" />

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, getInputStyles, sharedInputStyle, Tooltip, Icon, Spinner } from '@grafana/ui'; import { useStyles2, getInputStyles, sharedInputStyle, Tooltip, Icon, Spinner } from '@grafana/ui';
import { getFocusStyles } from '@grafana/ui/src/themes/mixins'; import { getFocusStyles } from '@grafana/ui/src/themes/mixins';
import { Trans } from 'app/core/internationalization';
import { Role } from '../../../types'; import { Role } from '../../../types';
@ -125,7 +126,11 @@ export const RolesLabel = ({ showBuiltInRole, numberOfRoles, appliedRoles }: Rol
}`}</ValueContainer> }`}</ValueContainer>
</Tooltip> </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 { Button, ScrollContainer, Stack, TextLink, useStyles2, useTheme2 } from '@grafana/ui';
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
import { Trans } from 'app/core/internationalization';
import { OrgRole, Role } from 'app/types'; import { OrgRole, Role } from 'app/types';
import { BuiltinRoleSelector } from './BuiltinRoleSelector'; import { BuiltinRoleSelector } from './BuiltinRoleSelector';
@ -35,7 +36,7 @@ const fixedRoleGroupNames: Record<string, string> = {
}; };
const tooltipMessage = ( 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 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; information in&nbsp;
<TextLink <TextLink
@ -46,7 +47,7 @@ const tooltipMessage = (
our documentation our documentation
</TextLink> </TextLink>
. .
</> </Trans>
); );
interface RolePickerMenuProps { interface RolePickerMenuProps {
@ -275,7 +276,7 @@ export const RolePickerMenu = ({
<div className={customStyles.menuButtonRow}> <div className={customStyles.menuButtonRow}>
<Stack justifyContent="flex-end"> <Stack justifyContent="flex-end">
<Button size="sm" fill="text" onClick={onClearInternal} disabled={updateDisabled}> <Button size="sm" fill="text" onClick={onClearInternal} disabled={updateDisabled}>
Clear all <Trans i18nKey="role-picker.menu.clear-button">Clear all</Trans>
</Button> </Button>
<Button size="sm" onClick={onUpdateInternal} disabled={updateDisabled}> <Button size="sm" onClick={onUpdateInternal} disabled={updateDisabled}>
{apply ? `Apply` : `Update`} {apply ? `Apply` : `Update`}

View File

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

View File

@ -9,7 +9,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { ActionMeta, AsyncVirtualizedSelect, Input, InputActionMeta, useStyles2 } from '@grafana/ui'; import { ActionMeta, AsyncVirtualizedSelect, Input, InputActionMeta, useStyles2 } from '@grafana/ui';
import appEvents from 'app/core/app_events'; 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 { contextSrv } from 'app/core/services/context_srv';
import { createFolder, getFolderByUid, searchFolders } from 'app/features/manage-dashboards/state/actions'; import { createFolder, getFolderByUid, searchFolders } from 'app/features/manage-dashboards/state/actions';
import { DashboardSearchHit } from 'app/features/search/types'; import { DashboardSearchHit } from 'app/features/search/types';
@ -323,7 +323,9 @@ export function OldFolderPicker(props: Props) {
return ( return (
<> <>
<FolderWarningWhenCreating /> <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 <Input
width={30} width={30}
autoFocus={true} autoFocus={true}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { components, MultiValueRemoveProps } from 'react-select';
import { escapeStringForRegex, GrafanaTheme2, SelectableValue } from '@grafana/data'; import { escapeStringForRegex, GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Icon, MultiSelect, useStyles2 } from '@grafana/ui'; 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 { TagBadge, getStyles as getTagBadgeStyles } from './TagBadge';
import { TagOption, TagSelectOption } from './TagOption'; import { TagOption, TagSelectOption } from './TagOption';
@ -156,7 +156,7 @@ export const TagFilter = ({
<div className={styles.tagFilter}> <div className={styles.tagFilter}>
{isClearable && tags.length > 0 && ( {isClearable && tags.length > 0 && (
<button className={styles.clear} onClick={() => onTagChange([])}> <button className={styles.clear} onClick={() => onTagChange([])}>
Clear tags <Trans i18nKey="tag-filter.clear-button">Clear tags</Trans>
</button> </button>
)} )}
<MultiSelect key={selectKey} {...selectOptions} prefix={<Icon name="tag-alt" />} aria-label="Tag filter" /> <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 { GrafanaTheme2 } from '@grafana/data';
import { reportExperimentView } from '@grafana/runtime/src'; import { reportExperimentView } from '@grafana/runtime/src';
import { Button, Icon, LinkButton, useStyles2 } from '@grafana/ui'; import { Button, Icon, LinkButton, useStyles2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
type ComponentSize = 'sm' | 'md'; type ComponentSize = 'sm' | 'md';
@ -36,7 +37,11 @@ export const UpgradeBox = ({
<Icon name={'rocket'} className={styles.icon} /> <Icon name={'rocket'} className={styles.icon} />
<div className={styles.inner}> <div className={styles.inner}>
<p className={styles.text}> <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> </p>
<LinkButton <LinkButton
variant="secondary" variant="secondary"
@ -46,7 +51,7 @@ export const UpgradeBox = ({
target="__blank" target="__blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Upgrade <Trans i18nKey="upgrade-box.upgrade-button">Upgrade</Trans>
</LinkButton> </LinkButton>
</div> </div>
</div> </div>
@ -130,7 +135,9 @@ export const UpgradeContent = ({
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.content}> <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>} {description && <h6 className={styles.description}>{description}</h6>}
<ul className={styles.list}> <ul className={styles.list}>
{listItems.map((item, index) => ( {listItems.map((item, index) => (
@ -151,7 +158,7 @@ export const UpgradeContent = ({
)} )}
{featureUrl && ( {featureUrl && (
<LinkButton fill={'text'} href={featureUrl} className={styles.link} target="_blank" rel="noreferrer noopener"> <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> </LinkButton>
)} )}
</div> </div>
@ -221,10 +228,12 @@ export const UpgradeContentVertical = ({
const styles = useStyles2(getContentVerticalStyles); const styles = useStyles2(getContentVerticalStyles);
return ( return (
<div className={styles.container}> <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>} {description && <h6 className={styles.description}>{description}</h6>}
<LinkButton fill={'text'} href={featureUrl} target="_blank" rel="noreferrer noopener"> <LinkButton fill={'text'} href={featureUrl} target="_blank" rel="noreferrer noopener">
Learn more <Trans i18nKey="upgrade-box.learn-more">Learn more</Trans>
</LinkButton> </LinkButton>
<div className={styles.media}> <div className={styles.media}>
<img src={getImgUrl(image)} alt={'Feature screenshot'} /> <img src={getImgUrl(image)} alt={'Feature screenshot'} />

View File

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

View File

@ -6,6 +6,7 @@ import { GrafanaTheme2, locationUtil, PageLayoutType } from '@grafana/data';
import { Button, ErrorWithStack, useStyles2 } from '@grafana/ui'; import { Button, ErrorWithStack, useStyles2 } from '@grafana/ui';
import { Page } from '../components/Page/Page'; import { Page } from '../components/Page/Page';
import { Trans } from '../internationalization';
interface Props { interface Props {
error: Error | null; error: Error | null;
@ -31,12 +32,18 @@ export function GrafanaRouteError({ error, errorInfo }: Props) {
<div className={styles.container}> <div className={styles.container}>
{isChunkLoadingError && ( {isChunkLoadingError && (
<div> <div>
<h2>Unable to find application file</h2> <h2>
<Trans i18nKey="route-error.title">Unable to find application file</Trans>
</h2>
<br /> <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 /> <br />
<Button size="md" variant="secondary" icon="repeat" onClick={() => window.location.reload()}> <Button size="md" variant="secondary" icon="repeat" onClick={() => window.location.reload()}>
Reload <Trans i18nKey="route-error.reload-button">Reload</Trans>
</Button> </Button>
<ErrorWithStack title={'Error details'} error={error} errorInfo={errorInfo} /> <ErrorWithStack title={'Error details'} error={error} errorInfo={errorInfo} />
</div> </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 { Action, DataFrame, VariableSuggestion } from '@grafana/data';
import { Button } from '@grafana/ui/src/components/Button'; import { Button } from '@grafana/ui/src/components/Button';
import { Modal } from '@grafana/ui/src/components/Modal/Modal'; import { Modal } from '@grafana/ui/src/components/Modal/Modal';
import { Trans } from 'app/core/internationalization';
import { ActionEditor } from './ActionEditor'; import { ActionEditor } from './ActionEditor';
@ -36,7 +37,7 @@ export const ActionEditorModalContent = ({
/> />
<Modal.ButtonRow> <Modal.ButtonRow>
<Button variant="secondary" onClick={() => onCancel(index)} fill="outline"> <Button variant="secondary" onClick={() => onCancel(index)} fill="outline">
Cancel <Trans i18nKey="action-editor.modal.cancel-button">Cancel</Trans>
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
@ -44,7 +45,7 @@ export const ActionEditorModalContent = ({
}} }}
disabled={dirtyAction.title.trim() === '' || dirtyAction.fetch.url.trim() === ''} disabled={dirtyAction.title.trim() === '' || dirtyAction.fetch.url.trim() === ''}
> >
Save <Trans i18nKey="action-editor.modal.save-button">Save</Trans>
</Button> </Button>
</Modal.ButtonRow> </Modal.ButtonRow>
</> </>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,7 +57,7 @@ export const DashboardsListModal = ({ email, onDismiss }: { email: string; onDis
</Trans> </Trans>
)} )}
</a> </a>
<span className={styles.urlsDivider}></span> <span className={styles.urlsDivider}>{'•'}</span>
<a <a
className={cx('external-link', styles.url)} className={cx('external-link', styles.url)}
href={generatePublicDashboardConfigUrl(dash.dashboardUid, dash.slug)} 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">{user.lastSeenAtAge}</td>
<td className="max-width-10"> <td className="max-width-10">
<Stack gap={2}> <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} /> <DashboardsListModalButton email={user.email} />
</Stack> </Stack>
</td> </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 { fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicker/api';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker'; import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { AccessControlAction, Organization, OrgRole, Role, UserDTO, UserOrg } from 'app/types'; import { AccessControlAction, Organization, OrgRole, Role, UserDTO, UserOrg } from 'app/types';
import { OrgRolePicker } from './OrgRolePicker'; import { OrgRolePicker } from './OrgRolePicker';
@ -60,7 +61,9 @@ export class UserOrgs extends PureComponent<Props, State> {
const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser; const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser;
return ( return (
<div> <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"> <Stack gap={1.5} direction="column">
<table className="filter-table form-inline"> <table className="filter-table form-inline">
<tbody> <tbody>
@ -80,7 +83,7 @@ export class UserOrgs extends PureComponent<Props, State> {
<div> <div>
{canAddToOrg && ( {canAddToOrg && (
<Button variant="secondary" onClick={this.showOrgAddModal} ref={this.addToOrgButtonRef}> <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> </Button>
)} )}
</div> </div>
@ -242,7 +245,7 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps> {
onCancel={this.onCancelClick} onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove} onConfirm={this.onOrgRemove}
> >
Remove from organization {t('admin.user-orgs.remove-button', 'Remove from organization')}
</ConfirmButton> </ConfirmButton>
)} )}
</td> </td>
@ -383,10 +386,10 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
<Modal.ButtonRow> <Modal.ButtonRow>
<Stack gap={2} justifyContent="center"> <Stack gap={2} justifyContent="center">
<Button variant="secondary" fill="outline" onClick={this.onCancel}> <Button variant="secondary" fill="outline" onClick={this.onCancel}>
Cancel <Trans i18nKey="admin.user-orgs-modal.cancel-button">Cancel</Trans>
</Button> </Button>
<Button variant="primary" disabled={selectedOrg === null} onClick={this.onAddUserToOrg}> <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> </Button>
</Stack> </Stack>
</Modal.ButtonRow> </Modal.ButtonRow>
@ -438,6 +441,7 @@ export function ChangeOrgButton({
interactive={true} interactive={true}
content={ content={
<div> <div>
<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 This user&apos;s role is not editable because it is synchronized from your auth provider. Refer to
the&nbsp; the&nbsp;
<a <a
@ -449,6 +453,7 @@ export function ChangeOrgButton({
Grafana authentication docs Grafana authentication docs
</a> </a>
&nbsp;for details. &nbsp;for details.
</Trans>
</div> </div>
} }
> >
@ -465,7 +470,7 @@ export function ChangeOrgButton({
onConfirm={onOrgRoleSave} onConfirm={onOrgRoleSave}
disabled={isExternalUser} disabled={isExternalUser}
> >
Change role {t('admin.user-orgs.change-role-button', 'Change role')}
</ConfirmButton> </ConfirmButton>
)} )}
</div> </div>
@ -486,8 +491,9 @@ export const ExternalUserTooltip = ({ lockMessage }: ExternalUserTooltipProps) =
interactive={true} interactive={true}
content={ content={
<div> <div>
This user&apos;s built-in role is not editable because it is synchronized from your auth provider. Refer to <Trans i18nKey="admin.user-orgs.external-user-tooltip">
the&nbsp; This user&apos;s built-in role is not editable because it is synchronized from your auth provider. Refer
to the&nbsp;
<a <a
className={styles.tooltipItemLink} className={styles.tooltipItemLink}
href={'https://grafana.com/docs/grafana/latest/auth'} href={'https://grafana.com/docs/grafana/latest/auth'}
@ -497,6 +503,7 @@ export const ExternalUserTooltip = ({ lockMessage }: ExternalUserTooltipProps) =
Grafana authentication docs Grafana authentication docs
</a> </a>
&nbsp;for details. &nbsp;for details.
</Trans>
</div> </div>
} }
> >

View File

@ -4,6 +4,7 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { ConfirmButton, RadioButtonGroup, Icon, useStyles2 } from '@grafana/ui'; import { ConfirmButton, RadioButtonGroup, Icon, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { ExternalUserTooltip } from 'app/features/admin/UserOrgs'; import { ExternalUserTooltip } from 'app/features/admin/UserOrgs';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
@ -39,11 +40,15 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
return ( return (
<div> <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"> <table className="filter-table form-inline">
<tbody> <tbody>
<tr> <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 ? ( {isEditing ? (
<td colSpan={2}> <td colSpan={2}>
<RadioButtonGroup <RadioButtonGroup
@ -57,10 +62,10 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
<td colSpan={2}> <td colSpan={2}>
{isGrafanaAdmin ? ( {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> </td>
)} )}
@ -72,7 +77,7 @@ export function UserPermissions({ isGrafanaAdmin, isExternalUser, lockMessage, o
onCancel={onCancelClick} onCancel={onCancelClick}
confirmText="Change" confirmText="Change"
> >
Change {t('admin.user-permissions.change-button', 'Change')}
</ConfirmButton> </ConfirmButton>
)} )}
{isExternalUser && ( {isExternalUser && (

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import {
Tooltip, Tooltip,
} from '@grafana/ui'; } from '@grafana/ui';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { Trans } from 'app/core/internationalization';
import { UserDTO } from 'app/types'; import { UserDTO } from 'app/types';
import { OrgUnits } from './OrgUnits'; import { OrgUnits } from './OrgUnits';
@ -101,10 +102,12 @@ export const UsersTable = ({
cell: ({ cell: { value } }: Cell<'licensedRole'>) => { cell: ({ cell: { value } }: Cell<'licensedRole'>) => {
return value === 'None' ? ( return value === 'None' ? (
<Text color={'disabled'}> <Text color={'disabled'}>
<Trans i18nKey="admin.users-table.no-licensed-role">
Not assigned{' '} Not assigned{' '}
<Tooltip placement="top" content="A licensed role will be assigned when this user signs in"> <Tooltip placement="top" content="A licensed role will be assigned when this user signs in">
<Icon name="question-circle" /> <Icon name="question-circle" />
</Tooltip> </Tooltip>
</Trans>
</Text> </Text>
) : ( ) : (
value value
@ -121,7 +124,21 @@ export const UsersTable = ({
iconName: 'question-circle', iconName: 'question-circle',
}, },
cell: ({ cell: { value } }: Cell<'lastSeenAtAge'>) => { 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(), 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 { useMemo } from 'react';
import { Alert, CellProps, Column, Icon, InteractiveTable, Stack, Text, Tooltip } from '@grafana/ui'; 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'; import { AppNotificationSeverity, LdapConnectionInfo, LdapServerInfo } from 'app/types';
interface Props { interface Props {
@ -54,7 +55,7 @@ export const LdapConnectionStatus = ({ ldapConnectionInfo }: Props) => {
<section> <section>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<Text color="primary" element="h3"> <Text color="primary" element="h3">
LDAP Connection <Trans i18nKey="admin.ldap-status.title">LDAP Connection</Trans>
</Text> </Text>
<InteractiveTable data={data} columns={columns} getRowId={(serverInfo) => serverInfo.host + serverInfo.port} /> <InteractiveTable data={data} columns={columns} getRowId={(serverInfo) => serverInfo.host + serverInfo.port} />
<LdapErrorBox ldapConnectionInfo={ldapConnectionInfo} /> <LdapErrorBox ldapConnectionInfo={ldapConnectionInfo} />
@ -83,7 +84,7 @@ export const LdapErrorBox = ({ ldapConnectionInfo }: LdapConnectionErrorProps) =
const errorElements = connectionErrors.map((info, index) => ( const errorElements = connectionErrors.map((info, index) => (
<div key={index}> <div key={index}>
<span style={{ fontWeight: 500 }}> <span style={{ fontWeight: 500 }}>
{info.host}:{info.port} {`${info.host}:${info.port}`}
<br /> <br />
</span> </span>
<span>{info.error}</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 { Alert, Button, Field, Input, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page'; import { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { import {
AppNotificationSeverity, AppNotificationSeverity,
@ -118,7 +119,9 @@ export const LdapPage = ({
{canReadLDAPUser && ( {canReadLDAPUser && (
<section> <section>
<h3>Test user mapping</h3> <h3>
<Trans i18nKey="admin.ldap.test-mapping-heading">Test user mapping</Trans>
</h3>
<form onSubmit={handleSubmit(search)}> <form onSubmit={handleSubmit(search)}>
<Field label="Username"> <Field label="Username">
<Input <Input
@ -129,7 +132,7 @@ export const LdapPage = ({
defaultValue={queryParams.username} defaultValue={queryParams.username}
addonAfter={ addonAfter={
<Button variant="primary" type="submit"> <Button variant="primary" type="submit">
Run <Trans i18nKey="admin.ldap.test-mapping-run-button">Run</Trans>
</Button> </Button>
} }
/> />

View File

@ -1,5 +1,6 @@
import { dateTimeFormat } from '@grafana/data'; import { dateTimeFormat } from '@grafana/data';
import { InteractiveTable, Text } from '@grafana/ui'; import { InteractiveTable, Text } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { SyncInfo } from 'app/types'; import { SyncInfo } from 'app/types';
interface Props { interface Props {
@ -29,7 +30,9 @@ export const LdapSyncInfo = ({ ldapSyncInfo }: Props) => {
return ( return (
<section> <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} /> <InteractiveTable data={data} columns={columns} getRowId={(sync) => sync.syncAttribute} />
</section> </section>
); );

View File

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

View File

@ -1,4 +1,5 @@
import { Box, Stack, Text } from '@grafana/ui'; import { Box, Stack, Text } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { LdapUser } from 'app/types'; import { LdapUser } from 'app/types';
import { LdapUserGroups } from './LdapUserGroups'; import { LdapUserGroups } from './LdapUserGroups';
@ -21,7 +22,9 @@ export const LdapUserInfo = ({ ldapUser }: Props) => {
<LdapUserTeams teams={ldapUser.teams} /> <LdapUserTeams teams={ldapUser.teams} />
) : ( ) : (
<Box> <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> </Box>
)} )}
</Stack> </Stack>

View File

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

View File

@ -14,6 +14,9 @@
"permission-list": { "permission-list": {
"permission": "Permission" "permission": "Permission"
}, },
"permission-list-item": {
"inherited": "Inherited from folder"
},
"permissions": { "permissions": {
"add-label": "Add a permission", "add-label": "Add a permission",
"no-permissions": "There are no permissions", "no-permissions": "There are no permissions",
@ -25,6 +28,143 @@
"user": "User" "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": { "alert-labels": {
"button": { "button": {
"hide": "Hide common labels", "hide": "Hide common labels",
@ -371,6 +511,17 @@
"message": "No API keys found" "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": { "bookmarks-page": {
"empty": { "empty": {
"message": "It looks like you havent created any bookmarks yet", "message": "It looks like you havent created any bookmarks yet",
@ -406,15 +557,15 @@
}, },
"counts": { "counts": {
"alertRule_one": "{{count}} alert rule", "alertRule_one": "{{count}} alert rule",
"alertRule_other": "{{count}} alert rule", "alertRule_other": "{{count}} alert rules",
"dashboard_one": "{{count}} dashboard", "dashboard_one": "{{count}} dashboard",
"dashboard_other": "{{count}} dashboard", "dashboard_other": "{{count}} dashboards",
"folder_one": "{{count}} folder", "folder_one": "{{count}} folder",
"folder_other": "{{count}} folder", "folder_other": "{{count}} folders",
"libraryPanel_one": "{{count}} library panel", "libraryPanel_one": "{{count}} library panel",
"libraryPanel_other": "{{count}} library panel", "libraryPanel_other": "{{count}} library panels",
"total_one": "{{count}} item", "total_one": "{{count}} item",
"total_other": "{{count}} item" "total_other": "{{count}} items"
}, },
"dashboards-tree": { "dashboards-tree": {
"collapse-folder-button": "Collapse folder {{title}}", "collapse-folder-button": "Collapse folder {{title}}",
@ -442,6 +593,7 @@
"clear-selection": "Clear selection", "clear-selection": "Clear selection",
"empty-message": "No folders found", "empty-message": "No folders found",
"error-title": "Error loading folders", "error-title": "Error loading folders",
"non-folder-item": "Non-folder {{itemKind}} {{itemUID}}",
"search-placeholder": "Search folders", "search-placeholder": "Search folders",
"unknown-error": "Unknown error" "unknown-error": "Unknown error"
}, },
@ -883,6 +1035,12 @@
"time-range-label": "Lock time range" "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": { "errors": {
"dashboard-settings": { "dashboard-settings": {
"annotations": { "annotations": {
@ -1103,9 +1261,29 @@
"export-as-json-tooltip": "Export" "export-as-json-tooltip": "Export"
} }
}, },
"folder-filter": {
"clear-folder-button": "Clear folders"
},
"folder-picker": { "folder-picker": {
"create-instructions": "Press enter to create the new folder.",
"loading": "Loading folders..." "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": { "gen-ai": {
"apply-suggestion": "Apply", "apply-suggestion": "Apply",
"incomplete-request-error": "Sorry, I was unable to complete your request. Please try again.", "incomplete-request-error": "Sorry, I was unable to complete your request. Please try again.",
@ -1225,6 +1403,10 @@
} }
}, },
"help-modal": { "help-modal": {
"column-headers": {
"description": "Description",
"keys": "Keys"
},
"shortcuts-category": { "shortcuts-category": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
"focused-panel": "Focused panel", "focused-panel": "Focused panel",
@ -1475,6 +1657,9 @@
} }
}, },
"login": { "login": {
"divider": {
"connecting-text": "or"
},
"error": { "error": {
"blocked": "You have exceeded the number of login attempts for this user. Please try again later.", "blocked": "You have exceeded the number of login attempts for this user. Please try again later.",
"invalid-user-or-password": "Invalid username or password", "invalid-user-or-password": "Invalid username or password",
@ -1502,6 +1687,9 @@
"verify-email-label": "Send a verification email", "verify-email-label": "Send a verification email",
"verify-email-loading-label": "Sending email..." "verify-email-loading-label": "Sending email..."
}, },
"layout": {
"update-password": "Update your password"
},
"services": { "services": {
"sing-in-with-prefix": "Sign in with {{serviceName}}" "sing-in-with-prefix": "Sign in with {{serviceName}}"
}, },
@ -2147,6 +2335,9 @@
"no-matches": "No matches found", "no-matches": "No matches found",
"unsupported-layout": "Unsupported layout" "unsupported-layout": "Unsupported layout"
}, },
"panel-type-filter": {
"clear-button": "Clear types"
},
"playlist-edit": { "playlist-edit": {
"error-prefix": "Error loading playlist:", "error-prefix": "Error loading playlist:",
"form": { "form": {
@ -2240,6 +2431,10 @@
}, },
"empty-state": { "empty-state": {
"message": "No plugins found" "message": "No plugins found"
},
"plugin-help": {
"error": "An error occurred when loading help.",
"not-found": "No query help could be found."
} }
}, },
"profile": { "profile": {
@ -2433,6 +2628,10 @@
"dashboard-modal-title": "Public dashboards", "dashboard-modal-title": "Public dashboards",
"shared-dashboard-modal-title": "Shared dashboards" "shared-dashboard-modal-title": "Shared dashboards"
}, },
"table-body": {
"dashboard-count_one": "{{count}} dashboard",
"dashboard-count_other": "{{count}} dashboards"
},
"table-header": { "table-header": {
"activated-label": "Activated", "activated-label": "Activated",
"activated-tooltip": "Earliest time user has been an active user to a dashboard", "activated-tooltip": "Earliest time user has been an active user to a dashboard",
@ -2527,6 +2726,19 @@
"dismissable-button": "Close" "dismissable-button": "Close"
}, },
"role-picker": { "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": { "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>." "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" "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": { "save-dashboards": {
"name-exists": { "name-exists": {
"message-info": "A dashboard with the same name in the selected folder already exists, including recently deleted dashboards.", "message-info": "A dashboard with the same name in the selected folder already exists, including recently deleted dashboards.",
@ -2808,6 +3025,17 @@
}, },
"title": "Preferences" "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": { "silences": {
"empty-state": { "empty-state": {
"button-title": "Create silence", "button-title": "Create silence",
@ -2860,6 +3088,7 @@
"view-button": "View" "view-button": "View"
}, },
"tag-filter": { "tag-filter": {
"clear-button": "Clear tags",
"loading": "Loading...", "loading": "Loading...",
"no-tags": "No tags found", "no-tags": "No tags found",
"placeholder": "Filter by tag" "placeholder": "Filter by tag"
@ -2980,6 +3209,13 @@
"add-transformation-header": "Start transforming data" "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": { "user-orgs": {
"current-org-button": "Current", "current-org-button": "Current",
"name-column": "Name", "name-column": "Name",

View File

@ -14,6 +14,9 @@
"permission-list": { "permission-list": {
"permission": "Pęřmįşşįőʼn" "permission": "Pęřmįşşįőʼn"
}, },
"permission-list-item": {
"inherited": "Ĩʼnĥęřįŧęđ ƒřőm ƒőľđęř"
},
"permissions": { "permissions": {
"add-label": "Åđđ ä pęřmįşşįőʼn", "add-label": "Åđđ ä pęřmįşşįőʼn",
"no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş", "no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş",
@ -25,6 +28,143 @@
"user": "Ůşęř" "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": { "alert-labels": {
"button": { "button": {
"hide": "Ħįđę čőmmőʼn ľäþęľş", "hide": "Ħįđę čőmmőʼn ľäþęľş",
@ -371,6 +511,17 @@
"message": "Ńő ÅPĨ ĸęyş ƒőūʼnđ" "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": { "bookmarks-page": {
"empty": { "empty": {
"message": "Ĩŧ ľőőĸş ľįĸę yőū ĥävęʼnŧ čřęäŧęđ äʼny þőőĸmäřĸş yęŧ", "message": "Ĩŧ ľőőĸş ľįĸę yőū ĥävęʼnŧ čřęäŧęđ äʼny þőőĸmäřĸş yęŧ",
@ -406,15 +557,15 @@
}, },
"counts": { "counts": {
"alertRule_one": "{{count}} äľęřŧ řūľę", "alertRule_one": "{{count}} äľęřŧ řūľę",
"alertRule_other": "{{count}} äľęřŧ řūľę", "alertRule_other": "{{count}} äľęřŧ řūľęş",
"dashboard_one": "{{count}} đäşĥþőäřđ", "dashboard_one": "{{count}} đäşĥþőäřđ",
"dashboard_other": "{{count}} đäşĥþőäřđ", "dashboard_other": "{{count}} đäşĥþőäřđş",
"folder_one": "{{count}} ƒőľđęř", "folder_one": "{{count}} ƒőľđęř",
"folder_other": "{{count}} ƒőľđęř", "folder_other": "{{count}} ƒőľđęřş",
"libraryPanel_one": "{{count}} ľįþřäřy päʼnęľ", "libraryPanel_one": "{{count}} ľįþřäřy päʼnęľ",
"libraryPanel_other": "{{count}} ľįþřäřy päʼnęľ", "libraryPanel_other": "{{count}} ľįþřäřy päʼnęľş",
"total_one": "{{count}} įŧęm", "total_one": "{{count}} įŧęm",
"total_other": "{{count}} įŧęm" "total_other": "{{count}} įŧęmş"
}, },
"dashboards-tree": { "dashboards-tree": {
"collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}", "collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}",
@ -442,6 +593,7 @@
"clear-selection": "Cľęäř şęľęčŧįőʼn", "clear-selection": "Cľęäř şęľęčŧįőʼn",
"empty-message": "Ńő ƒőľđęřş ƒőūʼnđ", "empty-message": "Ńő ƒőľđęřş ƒőūʼnđ",
"error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş", "error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş",
"non-folder-item": "Ńőʼn-ƒőľđęř {{itemKind}} {{itemUID}}",
"search-placeholder": "Ŝęäřčĥ ƒőľđęřş", "search-placeholder": "Ŝęäřčĥ ƒőľđęřş",
"unknown-error": "Ůʼnĸʼnőŵʼn ęřřőř" "unknown-error": "Ůʼnĸʼnőŵʼn ęřřőř"
}, },
@ -883,6 +1035,12 @@
"time-range-label": "Ŀőčĸ ŧįmę řäʼnģę" "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": { "errors": {
"dashboard-settings": { "dashboard-settings": {
"annotations": { "annotations": {
@ -1103,9 +1261,29 @@
"export-as-json-tooltip": "Ēχpőřŧ" "export-as-json-tooltip": "Ēχpőřŧ"
} }
}, },
"folder-filter": {
"clear-folder-button": "Cľęäř ƒőľđęřş"
},
"folder-picker": { "folder-picker": {
"create-instructions": "Přęşş ęʼnŧęř ŧő čřęäŧę ŧĥę ʼnęŵ ƒőľđęř.",
"loading": "Ŀőäđįʼ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": { "gen-ai": {
"apply-suggestion": "Åppľy", "apply-suggestion": "Åppľy",
"incomplete-request-error": "Ŝőřřy, Ĩ ŵäş ūʼnäþľę ŧő čőmpľęŧę yőūř řęqūęşŧ. Pľęäşę ŧřy äģäįʼn.", "incomplete-request-error": "Ŝőřřy, Ĩ ŵäş ūʼnäþľę ŧő čőmpľęŧę yőūř řęqūęşŧ. Pľęäşę ŧřy äģäįʼn.",
@ -1225,6 +1403,10 @@
} }
}, },
"help-modal": { "help-modal": {
"column-headers": {
"description": "Đęşčřįpŧįőʼn",
"keys": "Ķęyş"
},
"shortcuts-category": { "shortcuts-category": {
"dashboard": "Đäşĥþőäřđ", "dashboard": "Đäşĥþőäřđ",
"focused-panel": "Főčūşęđ päʼnęľ", "focused-panel": "Főčūşęđ päʼnęľ",
@ -1475,6 +1657,9 @@
} }
}, },
"login": { "login": {
"divider": {
"connecting-text": "őř"
},
"error": { "error": {
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.", "blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
"invalid-user-or-password": "Ĩʼnväľįđ ūşęřʼnämę őř päşşŵőřđ", "invalid-user-or-password": "Ĩʼnväľįđ ūşęřʼnämę őř päşşŵőřđ",
@ -1502,6 +1687,9 @@
"verify-email-label": "Ŝęʼnđ ä vęřįƒįčäŧįőʼn ęmäįľ", "verify-email-label": "Ŝęʼnđ ä vęřįƒįčäŧįőʼn ęmäįľ",
"verify-email-loading-label": "Ŝęʼnđįʼnģ ęmäįľ..." "verify-email-loading-label": "Ŝęʼnđįʼnģ ęmäįľ..."
}, },
"layout": {
"update-password": "Ůpđäŧę yőūř päşşŵőřđ"
},
"services": { "services": {
"sing-in-with-prefix": "Ŝįģʼn įʼn ŵįŧĥ {{serviceName}}" "sing-in-with-prefix": "Ŝįģʼn įʼn ŵįŧĥ {{serviceName}}"
}, },
@ -2147,6 +2335,9 @@
"no-matches": "Ńő mäŧčĥęş ƒőūʼnđ", "no-matches": "Ńő mäŧčĥęş ƒőūʼnđ",
"unsupported-layout": "Ůʼnşūppőřŧęđ ľäyőūŧ" "unsupported-layout": "Ůʼnşūppőřŧęđ ľäyőūŧ"
}, },
"panel-type-filter": {
"clear-button": "Cľęäř ŧypęş"
},
"playlist-edit": { "playlist-edit": {
"error-prefix": "Ēřřőř ľőäđįʼnģ pľäyľįşŧ:", "error-prefix": "Ēřřőř ľőäđįʼnģ pľäyľįşŧ:",
"form": { "form": {
@ -2240,6 +2431,10 @@
}, },
"empty-state": { "empty-state": {
"message": "Ńő pľūģįʼnş ƒőūʼnđ" "message": "Ńő pľūģįʼnş ƒőūʼnđ"
},
"plugin-help": {
"error": "Åʼn ęřřőř őččūřřęđ ŵĥęʼn ľőäđįʼnģ ĥęľp.",
"not-found": "Ńő qūęřy ĥęľp čőūľđ þę ƒőūʼnđ."
} }
}, },
"profile": { "profile": {
@ -2433,6 +2628,10 @@
"dashboard-modal-title": "Pūþľįč đäşĥþőäřđş", "dashboard-modal-title": "Pūþľįč đäşĥþőäřđş",
"shared-dashboard-modal-title": "Ŝĥäřęđ đäşĥþőäřđş" "shared-dashboard-modal-title": "Ŝĥäřęđ đäşĥþőäřđş"
}, },
"table-body": {
"dashboard-count_one": "{{count}} đäşĥþőäřđ",
"dashboard-count_other": "{{count}} đäşĥþőäřđş"
},
"table-header": { "table-header": {
"activated-label": "Åčŧįväŧęđ", "activated-label": "Åčŧįväŧęđ",
"activated-tooltip": "Ēäřľįęşŧ ŧįmę ūşęř ĥäş þęęʼn äʼn äčŧįvę ūşęř ŧő ä đäşĥþőäřđ", "activated-tooltip": "Ēäřľįęşŧ ŧįmę ūşęř ĥäş þęęʼn äʼn äčŧįvę ūşęř ŧő ä đäşĥþőäřđ",
@ -2527,6 +2726,19 @@
"dismissable-button": "Cľőşę" "dismissable-button": "Cľőşę"
}, },
"role-picker": { "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": { "title": {
"description": "Åşşįģʼn řőľęş ŧő ūşęřş ŧő ęʼnşūřę ģřäʼnūľäř čőʼnŧřőľ ővęř äččęşş ŧő Ğřäƒäʼnä&ľşqūő;ş ƒęäŧūřęş äʼnđ řęşőūřčęş. Fįʼnđ őūŧ mőřę įʼn őūř <2>đőčūmęʼnŧäŧįőʼn</2>." "description": "Åşşįģʼn řőľęş ŧő ūşęřş ŧő ęʼnşūřę ģřäʼnūľäř čőʼnŧřőľ ővęř äččęşş ŧő Ğřäƒäʼnä&ľşqūő;ş ƒęäŧūřęş äʼnđ řęşőūřčęş. Fįʼnđ őūŧ mőřę įʼn őūř <2>đőčūmęʼnŧäŧįőʼn</2>."
} }
@ -2536,6 +2748,11 @@
"label": "ßäşįč Ŗőľęş" "label": "ßäşįč Ŗőľęş"
} }
}, },
"route-error": {
"description": "Ğřäƒäʼnä ĥäş ľįĸęľy þęęʼn ūpđäŧęđ. Pľęäşę ŧřy řęľőäđįʼnģ ŧĥę päģę.",
"reload-button": "Ŗęľőäđ",
"title": "Ůʼnäþľę ŧő ƒįʼnđ äppľįčäŧįőʼn ƒįľę"
},
"save-dashboards": { "save-dashboards": {
"name-exists": { "name-exists": {
"message-info": "Å đäşĥþőäřđ ŵįŧĥ ŧĥę şämę ʼnämę įʼn ŧĥę şęľęčŧęđ ƒőľđęř äľřęäđy ęχįşŧş, įʼnčľūđįʼnģ řęčęʼnŧľy đęľęŧęđ đäşĥþőäřđş.", "message-info": "Å đäşĥþőäřđ ŵįŧĥ ŧĥę şämę ʼnämę įʼn ŧĥę şęľęčŧęđ ƒőľđęř äľřęäđy ęχįşŧş, įʼnčľūđįʼnģ řęčęʼnŧľy đęľęŧęđ đäşĥþőäřđş.",
@ -2808,6 +3025,17 @@
}, },
"title": "Přęƒęřęʼnčęş" "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": { "silences": {
"empty-state": { "empty-state": {
"button-title": "Cřęäŧę şįľęʼnčę", "button-title": "Cřęäŧę şįľęʼnčę",
@ -2860,6 +3088,7 @@
"view-button": "Vįęŵ" "view-button": "Vįęŵ"
}, },
"tag-filter": { "tag-filter": {
"clear-button": "Cľęäř ŧäģş",
"loading": "Ŀőäđįʼnģ...", "loading": "Ŀőäđįʼnģ...",
"no-tags": "Ńő ŧäģş ƒőūʼnđ", "no-tags": "Ńő ŧäģş ƒőūʼnđ",
"placeholder": "Fįľŧęř þy ŧäģ" "placeholder": "Fįľŧęř þy ŧäģ"
@ -2980,6 +3209,13 @@
"add-transformation-header": "Ŝŧäřŧ ŧřäʼnşƒőřmįʼnģ đäŧä" "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": { "user-orgs": {
"current-org-button": "Cūřřęʼnŧ", "current-org-button": "Cūřřęʼnŧ",
"name-column": "Ńämę", "name-column": "Ńämę",