]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23596 New branding
authorJeremy Davis <jeremy.davis@sonarsource.com>
Tue, 5 Nov 2024 17:07:01 +0000 (18:07 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 13 Nov 2024 20:05:48 +0000 (20:05 +0000)
SONAR-23597 SONAR-23598 SONAR-23595

61 files changed:
server/sonar-web/config/jest/SetupFailOnConsole.ts
server/sonar-web/package.json
server/sonar-web/public/images/SonarLint-connection-ok.png [deleted file]
server/sonar-web/public/images/SonarLint-connection-request.png [deleted file]
server/sonar-web/public/images/sq-sl.svg [deleted file]
server/sonar-web/src/main/js/app/components/AdminContainer.tsx
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx
server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx
server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx
server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx
server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx
server/sonar-web/src/main/js/app/index.ts
server/sonar-web/src/main/js/apps/account/Account.tsx
server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx
server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx
server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx
server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx
server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx
server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx
server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
server/sonar-web/src/main/js/apps/sessions/components/Login.tsx
server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx
server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx
server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx
server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
server/sonar-web/src/main/js/components/tutorials/test-utils.ts
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx
server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts
server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts
server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
server/sonar-web/src/main/js/helpers/l10n.ts
server/sonar-web/src/main/js/helpers/l10nBundle.ts
server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts
server/sonar-web/yarn.lock
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 182cafd7e37df7abcafc6ef222240206097fc114..251becf19780bda9e06927b44276408c006fa0c4 100644 (file)
@@ -22,6 +22,9 @@ import failOnConsole from 'jest-fail-on-console';
 const IGNORED_ERROR_MESSAGES: string[] = [
   // react-virtualized & react-draggable use `findDOMNode` which is deprecated
   'findDOMNode is deprecated and will be removed in the next major release',
+
+  // react-intl warning
+  '[@formatjs/intl] "defaultRichTextElements" was specified but "message" was not pre-compiled.',
 ];
 
 failOnConsole({
index cba1b568d504e4b3a8596034af74c7f84349c31d..cdc60b42ce72c50891210aa6c8b14957dba8d6b3 100644 (file)
@@ -10,7 +10,7 @@
     "@primer/octicons-react": "19.11.0",
     "@react-spring/rafz": "9.7.4",
     "@react-spring/web": "9.7.4",
-    "@sonarsource/echoes-react": "0.9.0",
+    "@sonarsource/echoes-react": "0.10.1",
     "@tanstack/react-query": "5.56.2",
     "@types/validator": "13.12.2",
     "axios": "1.7.7",
diff --git a/server/sonar-web/public/images/SonarLint-connection-ok.png b/server/sonar-web/public/images/SonarLint-connection-ok.png
deleted file mode 100644 (file)
index e4071d0..0000000
Binary files a/server/sonar-web/public/images/SonarLint-connection-ok.png and /dev/null differ
diff --git a/server/sonar-web/public/images/SonarLint-connection-request.png b/server/sonar-web/public/images/SonarLint-connection-request.png
deleted file mode 100644 (file)
index 421a79b..0000000
Binary files a/server/sonar-web/public/images/SonarLint-connection-request.png and /dev/null differ
diff --git a/server/sonar-web/public/images/sq-sl.svg b/server/sonar-web/public/images/sq-sl.svg
deleted file mode 100644 (file)
index 7686098..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 88"><defs><style>.cls-1{fill:#cb2029;}.cls-2{fill:#549dd0;}.cls-3{fill:#a0a5a8;}</style></defs><g id="Group_7691" data-name="Group 7691"><path id="Path_10468" data-name="Path 10468" class="cls-1" d="M15,87.26a14,14,0,1,1,14-14,14,14,0,0,1-14,14m0-25.42A11.44,11.44,0,1,0,26.43,73.28,11.46,11.46,0,0,0,15,61.84"/></g><path id="Path_10469" data-name="Path 10469" class="cls-1" d="M24.78,72.54c-.37-.73-.83-1.65-1.73-1.65s-1.35.91-1.72,1.65-.54,1-1,1-.62-.35-1-1-.82-1.65-1.72-1.65-1.35.91-1.73,1.65-.53,1-1,1-.62-.35-1-1-.82-1.65-1.72-1.65-1.36.91-1.73,1.65-.53,1-1,1-.63-.35-1-1-.82-1.65-1.72-1.65-1.36.87-1.72,1.6c0,0-.28.62-.4.91s-.6,1.41-.17,1.95c.25.3.6-.13.8-.45s.5-.94.5-.94c.37-.65.56-1,1-1s.62.36,1,1,.82,1.64,1.73,1.64S11,74.73,11.32,74s.54-1,1-1,.62.36,1,1,.83,1.64,1.73,1.64,1.35-.91,1.72-1.64.54-1,1-1,.62.36,1,1,.82,1.64,1.72,1.64,1.35-.91,1.73-1.64.53-1,1-1,.62.36,1,1l.38.7c.1.17.48.8.81.78s.4-1,.07-1.77C25.06,73.17,24.78,72.54,24.78,72.54Z"/><g id="SonarQube_Icon" data-name="SonarQube Icon"><path id="Path_10444" data-name="Path 10444" class="cls-2" d="M26.24,29.05H24.61A24.1,24.1,0,0,0,.37,5.15V3.52A25.73,25.73,0,0,1,26.24,29.05Z"/><path id="Path_10445" data-name="Path 10445" class="cls-2" d="M27.37,20.26a24.64,24.64,0,0,0-17-17.58l.38-1.31A26,26,0,0,1,28.7,20Z"/><path id="Path_10446" data-name="Path 10446" class="cls-2" d="M28.63,12.5A25,25,0,0,0,18.92,1.67l.57-.93A26.19,26.19,0,0,1,29.63,12.05Z"/></g><path class="cls-3" d="M16.25,42.09h4v2.33h-4V49H13.77V44.42h-4V42.09h4V37.85h2.48Z"/></svg>
\ No newline at end of file
index faa3e3c4fa3c99fc62de645fdfdd0477fd2244ad..65e122d5618aef2717c188d1a0e9a3b07ad22927 100644 (file)
@@ -26,7 +26,8 @@ import { getSettingsNavigation } from '../../api/navigation';
 import { getPendingPlugins } from '../../api/plugins';
 import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
 import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
-import { translate, translateWithParameters } from '../../helpers/l10n';
+import { translate } from '../../helpers/l10n';
+import { getIntl } from '../../helpers/l10nBundle';
 import { AdminPagesContext } from '../../types/admin';
 import { AppState } from '../../types/appstate';
 import { PendingPluginResult } from '../../types/plugins';
@@ -46,6 +47,7 @@ interface State {
 }
 
 export class AdminContainer extends React.PureComponent<AdminContainerProps, State> {
+  intl = getIntl();
   mounted = false;
   portalAnchor: Element | null = null;
   state: State = {
@@ -129,9 +131,9 @@ export class AdminContainer extends React.PureComponent<AdminContainerProps, Sta
       <>
         <Helmet
           defer={false}
-          titleTemplate={translateWithParameters(
-            'page_title.template.with_category',
-            translate('layout.settings'),
+          titleTemplate={this.intl.formatMessage(
+            { id: 'page_title.template.with_category' },
+            { page: translate('layout.settings') },
           )}
         />
         {this.portalAnchor &&
index 34913e58affdce9ac8ffeb8c7bb32c74725a934d..31fb9b35a42aa523aea1d2a7756e6483dcea8700 100644 (file)
@@ -22,6 +22,7 @@ import { differenceBy } from 'lodash';
 import * as React from 'react';
 import { createPortal } from 'react-dom';
 import { Helmet } from 'react-helmet-async';
+import { useIntl } from 'react-intl';
 import { Outlet } from 'react-router-dom';
 import { CenteredLayout, Spinner } from '~design-system';
 import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter';
@@ -31,7 +32,6 @@ import { validateProjectAlmBinding } from '../../api/alm-settings';
 import { getTasksForComponent } from '../../api/ce';
 import { getComponentData } from '../../api/components';
 import { getComponentNavigation } from '../../api/navigation';
-import { translateWithParameters } from '../../helpers/l10n';
 import { HttpStatus } from '../../helpers/request';
 import { getPortfolioUrl, getProjectUrl, getPullRequestUrl } from '../../helpers/urls';
 import { useCurrentBranchQuery } from '../../queries/branch';
@@ -63,6 +63,8 @@ function ComponentContainer({ hasFeature }: Readonly<WithAvailableFeaturesProps>
   } = useLocation();
   const router = useRouter();
 
+  const intl = useIntl();
+
   const [component, setComponent] = React.useState<Component>();
   const [currentTask, setCurrentTask] = React.useState<Task>();
   const [tasksInProgress, setTasksInProgress] = React.useState<Task[]>();
@@ -285,9 +287,9 @@ function ComponentContainer({ hasFeature }: Readonly<WithAvailableFeaturesProps>
     <div>
       <Helmet
         defer={false}
-        titleTemplate={translateWithParameters(
-          'page_title.template.with_instance',
-          component?.name ?? '',
+        titleTemplate={intl.formatMessage(
+          { id: 'page_title.template.with_instance' },
+          { project: component?.name ?? '' },
         )}
       />
       {component &&
index cce2c4419d6ed8da4b01a2b4acc792020cc345af..106ca9b14390b89935d01eb040e61a61c7c776b1 100644 (file)
@@ -33,6 +33,7 @@ import {
   Title,
 } from '~design-system';
 import { Image } from '~sonar-aligned/components/common/Image';
+import { SonarQubeConnectionIllustration } from '../../components/branding/SonarQubeConnectionIllustration';
 import { whenLoggedIn } from '../../components/hoc/whenLoggedIn';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { generateSonarLintUserToken, portIsValid, sendUserToken } from '../../helpers/sonarlint';
@@ -88,13 +89,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
       {status === Status.request && (
         <>
           <Title>{translate('sonarlint-connection.request.title')}</Title>
-          <Image
-            alt="sonarlint-connection-request"
-            className="sw-my-4"
-            src="/images/SonarLint-connection-request.png"
-          />
+          <SonarQubeConnectionIllustration className="sw-my-4" connected={false} />
           <p className="sw-my-4">
-            {translateWithParameters('sonarlint-connection.request.description', ideName)}
+            <FormattedMessage id="sonarlint-connection.request.description" values={{ ideName }} />
           </p>
           <p className="sw-mb-10">{translate('sonarlint-connection.request.description2')}</p>
 
@@ -110,7 +107,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
 
       {status === Status.tokenError && (
         <>
-          <Image alt="sonarlint-token-error" className="sw-my-4 sw-pt-2" src="/images/cross.svg" />
+          <Image aria-hidden className="sw-my-4 sw-pt-2" src="/images/cross.svg" />
           <Title>{translate('sonarlint-connection.token-error.title')}</Title>
           <p className="sw-my-4">{translate('sonarlint-connection.token-error.description')}</p>
           <p className="sw-mb-4">
@@ -131,11 +128,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
 
       {status === Status.tokenCreated && newToken && (
         <>
-          <Image
-            alt="sonarlint-connection-error"
-            className="sw-my-4 sw-pt-2"
-            src="/images/check.svg"
-          />
+          <Image aria-hidden className="sw-my-4 sw-pt-2" src="/images/check.svg" />
           <Title>{translate('sonarlint-connection.connection-error.title')}</Title>
           <p className="sw-my-6">
             {translate('sonarlint-connection.connection-error.description')}
@@ -167,11 +160,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
       {status === Status.tokenSent && newToken && (
         <>
           <Title>{translate('sonarlint-connection.success.title')}</Title>
-          <Image
-            alt="sonarlint-connection-success"
-            className="sw-mb-4"
-            src="/images/SonarLint-connection-ok.png"
-          />
+          <SonarQubeConnectionIllustration className="sw-mb-4" connected />
           <p className="sw-my-4">
             {translateWithParameters('sonarlint-connection.success.description', newToken.name)}
           </p>
index 54ed12766d7b7dd223c783bd4eade36604db4933..8dc51f4ca7c7cb7e6b08093e4302be9fe45c1ffe 100644 (file)
@@ -29,6 +29,14 @@ import { AdminPagesContext } from '../../../types/admin';
 import { AdminContainer, AdminContainerProps } from '../AdminContainer';
 import AdminContext from '../AdminContext';
 
+jest.mock('../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn() }),
+  };
+});
+
 jest.mock('../../../api/navigation', () => ({
   getSettingsNavigation: jest
     .fn()
index 70e84d95c9727c6f619b747b790a2d67881ee6fa..b9976eb3e213232b942e42e51de5882c22190734 100644 (file)
@@ -35,15 +35,15 @@ afterEach(() => {
 });
 
 it('should render the logged-in information', async () => {
-  renderGlobalFooter();
+  renderGlobalFooter({}, { edition: EditionKey.community });
 
   expect(ui.databaseWarningMessage.query()).not.toBeInTheDocument();
 
   expect(ui.footerListItems.getAll()).toHaveLength(7);
 
   expect(byText('Community Edition').get()).toBeInTheDocument();
-  expect(ui.versionLabel('4.2').get()).toBeInTheDocument();
-  expect(await ui.ltaDocumentationLinkActive.find()).toBeInTheDocument();
+  expect(await ui.versionLabel('4.2').find()).toBeInTheDocument();
+  expect(ui.ltaDocumentationLinkActive.query()).not.toBeInTheDocument();
   expect(ui.apiLink.get()).toBeInTheDocument();
 });
 
@@ -85,7 +85,7 @@ it('should not render missing logged-in information', () => {
 });
 
 it('should not render the logged-in information', () => {
-  renderGlobalFooter({ hideLoggedInInfo: true });
+  renderGlobalFooter({ hideLoggedInInfo: true }, { edition: EditionKey.community });
 
   expect(ui.databaseWarningMessage.query()).not.toBeInTheDocument();
 
@@ -109,7 +109,7 @@ function renderGlobalFooter(
   return renderComponent(<GlobalFooter {...props} />, '/', {
     appState: mockAppState({
       productionDatabase: true,
-      edition: EditionKey.community,
+      edition: EditionKey.developer,
       version: '4.2',
       ...appStateOverride,
     }),
index c1a7473aa79ddf2b61dba9cf89f3e6fca40c626c..be82875356ef7479653de80cfb6520cb6fdb5fed 100644 (file)
@@ -156,7 +156,6 @@ function renderInProgressBanner(completedCount: number, total: number) {
       <span>
         <FormattedMessage
           id="indexation.admin_link"
-          defaultMessage={translate('indexation.admin_link')}
           values={{
             link: renderBackgroundTasksPageLink(false, translate('background_tasks.page')),
           }}
index d29d1223ec46c5654bca62254530cdb5ac1b157d..7b06d3ddcd5a8b86c375ca866e3904f0aa933702 100644 (file)
@@ -24,7 +24,6 @@ import { CenteredLayout, FlagMessage, Link } from '~design-system';
 import withIndexationContext, {
   WithIndexationContextProps,
 } from '../../../components/hoc/withIndexationContext';
-import { translate } from '../../../helpers/l10n';
 
 export class PageUnavailableDueToIndexation extends React.PureComponent<WithIndexationContextProps> {
   componentDidUpdate() {
@@ -41,17 +40,14 @@ export class PageUnavailableDueToIndexation extends React.PureComponent<WithInde
       <CenteredLayout className="sw-flex sw-justify-around">
         <FlagMessage className="sw-mt-32" variant="info">
           <span className="sw-w-[290px]">
-            {translate('indexation.page_unavailable.description')}
+            <FormattedMessage id="indexation.page_unavailable.description" />
             <span className="sw-ml-1">
               <FormattedMessage
-                defaultMessage={translate(
-                  'indexation.page_unavailable.description.additional_information',
-                )}
                 id="indexation.page_unavailable.description.additional_information"
                 values={{
                   link: (
                     <Link to="https://docs.sonarsource.com/sonarqube/latest/instance-administration/reindexing/">
-                      {translate('learn_more')}
+                      <FormattedMessage id="learn_more" />
                     </Link>
                   ),
                 }}
index 3f72585ddc754bfa89c28212a9981380c07fac65..062673766cf342cd56ef9d164d5039fcb46b03d0 100644 (file)
  */
 
 import { Link } from '@sonarsource/echoes-react';
+import { useIntl } from 'react-intl';
 import { HelperHintIcon } from '~design-system';
 import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip';
 import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
 import { DocLink } from '../../../../../helpers/doc-links';
-import { translate, translateWithParameters } from '../../../../../helpers/l10n';
+import { translate } from '../../../../../helpers/l10n';
 import { getApplicationAdminUrl } from '../../../../../helpers/urls';
 import { useProjectBindingQuery } from '../../../../../queries/devops-integration';
 import { AlmKeys } from '../../../../../types/alm-settings';
@@ -48,6 +49,8 @@ export default function BranchHelpTooltip({
   const { data: projectBinding } = useProjectBindingQuery(component.key);
   const isGitLab = projectBinding != null && projectBinding.alm === AlmKeys.GitLab;
 
+  const intl = useIntl();
+
   if (isApplication) {
     if (!hasManyBranches && canAdminComponent) {
       return (
@@ -72,9 +75,11 @@ export default function BranchHelpTooltip({
         <DocHelpTooltip
           content={
             projectBinding != null
-              ? translateWithParameters(
-                  `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
-                  translate('alm', projectBinding.alm),
+              ? intl.formatMessage(
+                  {
+                    id: `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
+                  },
+                  { alm: translate('alm', projectBinding.alm) },
                 )
               : translate('branch_like_navigation.no_branch_support.content')
           }
index 22ce2083a081d3dc3acc3da0941346f46310d84f..094f55130f1c83a814c7d5672bda24044840b57c 100644 (file)
@@ -19,8 +19,8 @@
  */
 
 import { DropdownMenu, DropdownMenuAlign } from '@sonarsource/echoes-react';
+import { FormattedMessage } from 'react-intl';
 import { MainMenuItem } from '~design-system';
-import { translate } from '../../../../helpers/l10n';
 import { AppState } from '../../../../types/appstate';
 import { Extension } from '../../../../types/types';
 import withAppStateContext from '../../app-state/withAppStateContext';
@@ -48,7 +48,7 @@ function GlobalNavMore({ appState: { globalPages = [] } }: Readonly<{ appState:
     >
       <MainMenuItem>
         <a aria-haspopup="menu" href="#" id="global-navigation-more" role="button">
-          {translate('more')}
+          <FormattedMessage id="more" />
         </a>
       </MainMenuItem>
     </DropdownMenu.Root>
index dc200ed30e207e564555f75ab7b3506d49187c21..5a642219a17b132addd9704b821c441fe76858e0 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { LogoSize } from '@sonarsource/echoes-react';
 import * as React from 'react';
-import { MainAppBar, SonarQubeLogo } from '~design-system';
+import { MainAppBar } from '~design-system';
 import { Image } from '~sonar-aligned/components/common/Image';
+import { SonarQubeProductLogo } from '../../../../components/branding/SonarQubeProductLogo';
 import { translate } from '../../../../helpers/l10n';
 import { GlobalSettingKeys } from '../../../../types/settings';
 import { AppStateContext } from '../../app-state/AppStateContext';
@@ -41,7 +43,7 @@ function LogoWithAriaText() {
       {customLogoUrl ? (
         <Image alt={title} src={customLogoUrl} width={customLogoWidth} />
       ) : (
-        <SonarQubeLogo />
+        <SonarQubeProductLogo hasText size={LogoSize.Large} />
       )}
     </div>
   );
index 9e828b956e35a6b26c9949d2508c9d55e18a0496..8bf0ca1a0f9c9ed0c839523d42b3621016efc090 100644 (file)
  */
 
 import styled from '@emotion/styled';
-import { Button } from '@sonarsource/echoes-react';
+import { Button, Theme, ThemeProvider } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { ButtonPrimary, themeBorder, themeColor } from '~design-system';
-import { Image } from '~sonar-aligned/components/common/Image';
 import { dismissNotice } from '../../../api/users';
+import { SonarQubeIDEPromotionIllustration } from '../../../components/branding/SonarQubeIDEPromotionIllustration';
 import { translate } from '../../../helpers/l10n';
 import { NoticeType, isLoggedIn } from '../../../types/users';
 import { CurrentUserContextInterface } from '../current-user/CurrentUserContext';
@@ -48,9 +48,11 @@ export function PromotionNotification(props: CurrentUserContextInterface) {
 
   return (
     <PromotionNotificationWrapper className="it__promotion_notification sw-z-global-popup sw-rounded-1 sw-flex sw-items-center sw-px-4">
-      <div className="sw-mr-2">
-        <Image alt="SonarQube + SonarLint" height={80} src="/images/sq-sl.svg" />
-      </div>
+      <ThemeProvider theme={Theme.dark}>
+        <div className="sw-mr-2">
+          <SonarQubeIDEPromotionIllustration />
+        </div>
+      </ThemeProvider>
       <PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4">
         <span className="sw-typo-semibold">{translate('promotion.sonarlint.title')}</span>
         <p className="sw-mt-2">{translate('promotion.sonarlint.content')}</p>
index 8ad4712c238f5d794e95badb9465aac029ea74f5..e1b81ebe27336453eca8b073a0b811d49b9ef302 100644 (file)
@@ -52,10 +52,17 @@ async function initApplication() {
     },
   );
 
-  const [l10nBundle, currentUser, appState, availableFeatures] = await Promise.all([
-    loadL10nBundle(),
+  const appState = isMainApp()
+    ? await getGlobalNavigation().catch((error) => {
+        // eslint-disable-next-line no-console
+        console.error(error);
+        return undefined;
+      })
+    : undefined;
+
+  const [l10nBundle, currentUser, availableFeatures] = await Promise.all([
+    loadL10nBundle(appState),
     isMainApp() ? getCurrentUser() : undefined,
-    isMainApp() ? getGlobalNavigation() : undefined,
     isMainApp() ? getAvailableFeatures() : undefined,
   ]).catch((error) => {
     // eslint-disable-next-line no-console
index 8f16428327c1cda6a6cc6111421d3bd1beb67e57..8c7b227834ce567ef33760d0e1e38a48e8163e05 100644 (file)
 import * as React from 'react';
 import { createPortal } from 'react-dom';
 import { Helmet } from 'react-helmet-async';
+import { useIntl } from 'react-intl';
 import { Outlet } from 'react-router-dom';
 import { LargeCenteredLayout, PageContentFontWrapper, TopBar } from '~design-system';
 import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { useCurrentLoginUser } from '../../app/components/current-user/CurrentUserContext';
-import { translate, translateWithParameters } from '../../helpers/l10n';
+import { translate } from '../../helpers/l10n';
 import Nav from './components/Nav';
 import UserCard from './components/UserCard';
 
@@ -33,6 +34,8 @@ export default function Account() {
   const currentUser = useCurrentLoginUser();
   const [portalAnchor, setPortalAnchor] = React.useState<Element | null>(null);
 
+  const intl = useIntl();
+
   // Set portal anchor on mount
   React.useEffect(() => {
     setPortalAnchor(document.getElementById('component-nav-portal'));
@@ -61,9 +64,9 @@ export default function Account() {
           <Helmet
             defaultTitle={title}
             defer={false}
-            titleTemplate={translateWithParameters(
-              'page_title.template.with_category',
-              translate('my_account.page'),
+            titleTemplate={intl.formatMessage(
+              { id: 'page_title.template.with_category' },
+              { page: translate('my_account.page') },
             )}
           />
 
index 889e943b41f4f9e8449c989d16df950bd4071178..daf9032ca96bc9520f8ab903cf7a37a9ea7631d2 100644 (file)
@@ -53,6 +53,14 @@ jest.mock('../../../../helpers/dates', () => {
   };
 });
 
+jest.mock('../../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }),
+  };
+});
+
 const ui = {
   pageTitle: byRole('heading', { name: 'audit_logs.page' }),
   downloadButton: byRole('link', { name: 'download_verb' }),
index 37916c840b05ce57a8bd89fd628f1df2d35a8ae2..d59b743fc31b6d8945e8b3b90c2b83ab6d7b6675 100644 (file)
@@ -36,6 +36,15 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests';
 
 const rulesHandler = new CodingRulesServiceMock();
 const settingsHandler = new SettingsServiceMock();
+
+jest.mock('../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn() }),
+  };
+});
+
 afterEach(() => {
   settingsHandler.reset();
   rulesHandler.reset();
@@ -53,7 +62,7 @@ describe('rendering', () => {
     expect(ui.ruleCleanCodeAttribute(CleanCodeAttribute.Clear).get()).toBeInTheDocument();
     // 1 In Rule details + 1 in facet
     expect(ui.ruleSoftwareQuality(SoftwareQuality.Maintainability).getAll()).toHaveLength(2);
-    expect(document.title).toEqual('page_title.template.with_category.coding_rules.page');
+    expect(document.title).toEqual('coding_rule.page.Java.Awsome java rule');
     expect(screen.getByText('Why')).toBeInTheDocument();
     expect(screen.getByText('Because')).toBeInTheDocument();
 
index a68adfaaf8ba759196e221af3e5344946c8c0604..7e61a2f7aa9a837d557ca7df895fbab21b111392 100644 (file)
@@ -38,6 +38,14 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests';
 const rulesHandler = new CodingRulesServiceMock();
 const settingsHandler = new SettingsServiceMock();
 
+jest.mock('../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn() }),
+  };
+});
+
 afterEach(() => {
   rulesHandler.reset();
   settingsHandler.reset();
index 0deaa28759c379b5310a0b5ea3f3221503064694..73cbba1cfcc9e0a6037ab75d0876e169072d9427 100644 (file)
@@ -28,6 +28,14 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests';
 const rulesHandler = new CodingRulesServiceMock();
 const settingsHandler = new SettingsServiceMock();
 
+jest.mock('../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn() }),
+  };
+});
+
 afterEach(() => {
   rulesHandler.reset();
   settingsHandler.reset();
index 1625138526e3c37ab8624ebef4431075e393077d..77c670e76bee04ac92fa04542e06284cc34d42ad 100644 (file)
@@ -45,6 +45,7 @@ import { DocLink } from '../../../helpers/doc-links';
 import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
 import { KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getIntl } from '../../../helpers/l10nBundle';
 import { SecurityStandard } from '../../../types/security';
 import { SettingsKey } from '../../../types/settings';
 import { Dict, Paging, Rule, RuleActivation } from '../../../types/types';
@@ -103,6 +104,7 @@ interface State {
 const RULE_LIST_HEADER_HEIGHT = 68;
 
 export class CodingRulesApp extends React.PureComponent<Props, State> {
+  intl = getIntl();
   mounted = false;
 
   constructor(props: Props) {
@@ -574,9 +576,9 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
           <Helmet
             defer={false}
             title={translateWithParameters('coding_rule.page', openRule.langName, openRule.name)}
-            titleTemplate={translateWithParameters(
-              'page_title.template.with_category',
-              translate('coding_rules.page'),
+            titleTemplate={this.intl.formatMessage(
+              { id: 'page_title.template.with_category' },
+              { page: translate('coding_rules.page') },
             )}
           />
         ) : (
index 139b9a7fbdb42ea161c3206aee998d8965997ae3..2afc4120eb603b4157f665877e3b55c4c74c7aef 100644 (file)
@@ -60,13 +60,7 @@ const ui = {
   }),
 };
 
-const original = window.location;
-
 beforeAll(() => {
-  Object.defineProperty(window, 'location', {
-    configurable: true,
-    value: { replace: jest.fn() },
-  });
   almIntegrationHandler = new AlmIntegrationsServiceMock();
   dopTranslationHandler = new DopTranslationServiceMock();
   newCodePeriodHandler = new NewCodeDefinitionServiceMock();
@@ -78,9 +72,6 @@ beforeEach(() => {
   dopTranslationHandler.reset();
   newCodePeriodHandler.reset();
 });
-afterAll(() => {
-  Object.defineProperty(window, 'location', { configurable: true, value: original });
-});
 
 it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => {
   const user = userEvent.setup();
index d3ee2e653282b3e8a80cbe6dd51640fdc498e524..2bee7a314dfdbf6fdb7f7490374beacd7b1e44a6 100644 (file)
@@ -55,13 +55,8 @@ const ui = {
   }),
   instanceSelector: byLabelText(/alm.configuration.selector.label/),
 };
-const original = window.location;
 
 beforeAll(() => {
-  Object.defineProperty(window, 'location', {
-    configurable: true,
-    value: { replace: jest.fn() },
-  });
   almIntegrationHandler = new AlmIntegrationsServiceMock();
   dopTranslationHandler = new DopTranslationServiceMock();
   newCodePeriodHandler = new NewCodeDefinitionServiceMock();
@@ -74,10 +69,6 @@ beforeEach(() => {
   newCodePeriodHandler.reset();
 });
 
-afterAll(() => {
-  Object.defineProperty(window, 'location', { configurable: true, value: original });
-});
-
 it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => {
   const user = userEvent.setup();
   renderCreateProject();
index ca52dfa93b47ce75543c3835688136373a2cefd7..20417f5162a55c72a07bb1f6bcf060fe97311c6f 100644 (file)
@@ -83,13 +83,7 @@ const ui = {
   globalSettingRadio: byRole('radio', { name: 'new_code_definition.global_setting' }),
 };
 
-const original = window.location;
-
 beforeAll(() => {
-  Object.defineProperty(window, 'location', {
-    configurable: true,
-    value: { replace: jest.fn() },
-  });
   almIntegrationHandler = new AlmIntegrationsServiceMock();
   dopTranslationHandler = new DopTranslationServiceMock();
   newCodePeriodHandler = new NewCodeDefinitionServiceMock();
@@ -102,10 +96,6 @@ beforeEach(() => {
   newCodePeriodHandler.reset();
 });
 
-afterAll(() => {
-  Object.defineProperty(window, 'location', { configurable: true, value: original });
-});
-
 it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => {
   const user = userEvent.setup();
   renderCreateProject();
index ce4a24d6a91f3c2e66ab07c8204b3fc725bce952..a608af96767ef9b8df8f25baf49a398ed759015b 100644 (file)
@@ -77,10 +77,6 @@ const ui = {
 };
 
 beforeAll(() => {
-  Object.defineProperty(window, 'location', {
-    configurable: true,
-    value: { replace: jest.fn() },
-  });
   almIntegrationHandler = new AlmIntegrationsServiceMock();
   almSettingsHandler = new AlmSettingsServiceMock();
   componentsHandler = new ComponentsServiceMock();
index 051bdfb50ea7104fb4817aff60223b2636262dad..abf661e987bcf783d0c477952409b1d704adb492 100644 (file)
@@ -130,7 +130,7 @@ describe('issues app', () => {
 
       // Are rule headers present?
       expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
-      expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument();
+      expect(screen.getByRole('link', { name: /^advancedRuleId/ })).toBeInTheDocument();
 
       // Select the "why is this an issue" tab and check its content
       await user.click(
@@ -182,7 +182,7 @@ describe('issues app', () => {
 
       // Are rule headers present?
       expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
-      expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+      expect(screen.getByRole('link', { name: /^simpleRuleId/ })).toBeInTheDocument();
 
       // Select the "why is this an issue tab" and check its content
       await user.click(
@@ -195,7 +195,7 @@ describe('issues app', () => {
 
       // Are rule headers present?
       expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
-      expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+      expect(screen.getByRole('link', { name: /^simpleRuleId/ })).toBeInTheDocument();
 
       // The "Where is the issue" tab should be selected by default. Check its content
       expect(screen.getAllByRole('button', { name: 'Issue on file' })).toHaveLength(2); // there will be 2 buttons one in concise issue and other in code viewer
index b6f788ab3d050bc2beffefd7e62fb648f494c014..0179cd64c9a10a057339c27f81f89ef89b447adc 100644 (file)
@@ -21,6 +21,7 @@
 import styled from '@emotion/styled';
 import { Spinner } from '@sonarsource/echoes-react';
 import { Helmet } from 'react-helmet-async';
+import { useIntl } from 'react-intl';
 import {
   FlagMessage,
   LargeCenteredLayout,
@@ -33,7 +34,7 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe
 import { IssueSuggestionCodeTab } from '../../../components/rules/IssueSuggestionCodeTab';
 import IssueTabViewer from '../../../components/rules/IssueTabViewer';
 import { fillBranchLike } from '../../../helpers/branch-like';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import { useRuleDetailsQuery } from '../../../queries/rules';
 import A11ySkipTarget from '../../../sonar-aligned/components/a11y/A11ySkipTarget';
 import { isPortfolioLike } from '../../../sonar-aligned/helpers/component';
@@ -82,6 +83,8 @@ export default function IssueDetails({
   const { data: ruleData, isLoading: isLoadingRule } = useRuleDetailsQuery({ key: openIssue.rule });
   const openRuleDetails = ruleData?.rule;
 
+  const intl = useIntl();
+
   const warning = !canBrowseAllChildProjects && isPortfolioLike(qualifier) && (
     <FlagMessage
       className="it__portfolio_warning sw-flex"
@@ -100,9 +103,9 @@ export default function IssueDetails({
             <Helmet
               defer={false}
               title={openIssue.message}
-              titleTemplate={translateWithParameters(
-                'page_title.template.with_category',
-                translate('issues.page'),
+              titleTemplate={intl.formatMessage(
+                { id: 'page_title.template.with_category' },
+                { page: translate('issues.page') },
               )}
             />
             <h1 className="sw-sr-only">{translate('issues.page')}</h1>
index 99d2cccca84e0ad1c3067e7fafdf072fbdf6a933..cc01e0de5f07e56e165ff2a9e5e33c9555239abb 100644 (file)
@@ -165,7 +165,7 @@ export default class IssueHeader extends React.PureComponent<Props, State> {
         {isExternal ? (
           <span>({key})</span>
         ) : (
-          <Link to={getRuleUrl(key)} target="_blank">
+          <Link to={getRuleUrl(key)} shouldOpenInNewTab>
             {key}
           </Link>
         )}
index 155bbc8878bb86a9fe82c5ad70aaba792e725db4..4a2111cf2fed12d3efc714edcac1c44847c64a58 100644 (file)
 
 import { FormattedMessage } from 'react-intl';
 import { formatMeasure } from '~sonar-aligned/helpers/measures';
-import { translate } from '../../../helpers/l10n';
 
 export default function TotalEffort({ effort }: { effort: number }) {
   return (
     <div className="sw-inline-block">
       <FormattedMessage
-        defaultMessage={translate('issue.x_effort')}
         id="issue.x_effort"
         values={{ 0: <strong>{formatMeasure(effort, 'WORK_DUR')}</strong> }}
       />
index d14f7713ba4c8bcdce585a1dc0f09881b43c1603..c644d5e774c389351d997bf047213e3038d1d2ef 100644 (file)
@@ -26,6 +26,7 @@ import {
   IconMoreVertical,
   Tooltip,
 } from '@sonarsource/echoes-react';
+import { useIntl } from 'react-intl';
 import {
   ActionCell,
   Badge,
@@ -97,6 +98,8 @@ function referenceBranchDoesNotExist(
 export default function BranchListRow(props: BranchListRowProps) {
   const { branch, existingBranches, inheritedSetting } = props;
 
+  const intl = useIntl();
+
   let settingWarning: string | undefined;
   if (branchInheritsItselfAsReference(branch, inheritedSetting)) {
     settingWarning = translateWithParameters(
@@ -104,9 +107,11 @@ export default function BranchListRow(props: BranchListRowProps) {
       branch.name,
     );
   } else if (referenceBranchDoesNotExist(branch, existingBranches)) {
-    settingWarning = translateWithParameters(
-      'baseline.reference_branch.does_not_exist',
-      branch.newCodePeriod?.value ?? '',
+    settingWarning = intl.formatMessage(
+      {
+        id: 'baseline.reference_branch.does_not_exist',
+      },
+      { branch: branch.newCodePeriod?.value ?? '' },
     );
   }
 
index 10d64eb50c1746eaebc62fe15394c93609c12ea7..1b2456a3c110680fe687e9ddc910e9c9f1c50a13 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { FormattedMessage } from 'react-intl';
 import { MenuPlacement, OptionProps, components } from 'react-select';
 import { Badge, FlagErrorIcon, FormField, InputSelect, SelectionCard } from '~design-system';
 import Tooltip from '../../../components/controls/Tooltip';
 import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import { NewCodeDefinitionType } from '../../../types/new-code-definition';
 
 export interface BaselineSettingReferenceBranchProps {
@@ -60,10 +61,12 @@ function renderBranchOption(props: OptionProps<BranchOption, false>) {
     <components.Option {...props}>
       {option.isInvalid ? (
         <Tooltip
-          content={translateWithParameters(
-            'baseline.reference_branch.does_not_exist',
-            option.value,
-          )}
+          content={
+            <FormattedMessage
+              id="baseline.reference_branch.does_not_exist"
+              values={{ branch: option.value }}
+            />
+          }
         >
           <span>
             {option.value} <FlagErrorIcon className="sw-ml-2" />
index 8758e89fc38de95e91fb730741e3ce8e73cb5cc9..e2a7bc5e9a8a1c4aab981a8137b6faedbddb2ec4 100644 (file)
@@ -394,6 +394,7 @@ function getPageObjects() {
     branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
     dismissButton: byLabelText('dismiss'),
     baselineSpecificAnalysisDate: byText(/January 10, 2018/),
+    missingReferenceBranchWarning: byText('baseline.reference_branch.does_not_exist'),
   };
 
   async function appIsLoaded() {
index 9ea67fc27748ab1be4db97d00de62fa9c488fa12..93f608d26cd9d21e8294d01b9989c02ca220afe4 100644 (file)
@@ -164,7 +164,6 @@ function renderFirstLine(
               <span className="sw-typo-semibold" title={formattedAnalysisDate}>
                 <FormattedMessage
                   id="projects.last_analysis_on_x"
-                  defaultMessage={translate('projects.last_analysis_on_x')}
                   values={{
                     date: <DateFromNow className="sw-typo-default" date={analysisDate} />,
                   }}
index ad5d7b1510fc7d2365ac35d445ce951fff49ec2f..0a1b3363e22b5f0977ea1dfa2646ccbd43926e85 100644 (file)
@@ -22,6 +22,7 @@ import { withTheme } from '@emotion/react';
 import styled from '@emotion/styled';
 import { useCallback, useEffect } from 'react';
 import { Helmet } from 'react-helmet-async';
+import { useIntl } from 'react-intl';
 import { useNavigate, useParams } from 'react-router-dom';
 import {
   Card,
@@ -36,7 +37,7 @@ import {
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import '../../../components/search-navigator.css';
 import { DocLink } from '../../../helpers/doc-links';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import { getQualityGateUrl } from '../../../helpers/urls';
 import { useQualityGatesQuery } from '../../../queries/quality-gates';
 import { QualityGate } from '../../../types/types';
@@ -47,6 +48,7 @@ import ListHeader from './ListHeader';
 
 export default function App() {
   const { data, isLoading } = useQualityGatesQuery();
+  const intl = useIntl();
   const { name } = useParams();
   const navigate = useNavigate();
   const {
@@ -79,9 +81,9 @@ export default function App() {
       <PageContentFontWrapper className="sw-typo-default">
         <Helmet
           defer={false}
-          titleTemplate={translateWithParameters(
-            'page_title.template.with_category',
-            translate('quality_gates.page'),
+          titleTemplate={intl.formatMessage(
+            { id: 'page_title.template.with_category' },
+            { page: translate('quality_gates.page') },
           )}
         />
         <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-w-full">
index f8c72d29f8dd5661f5cdcd93c40f087bc650e028..a47c1873b6c09edca57ab5dd4dd89997a90d8ece 100644 (file)
 
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import { useIntl } from 'react-intl';
 import { Outlet, useSearchParams } from 'react-router-dom';
 import { useLocation } from '~sonar-aligned/components/hoc/withRouter';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import ProfileHeader from '../details/ProfileHeader';
 import { useQualityProfilesContext } from '../qualityProfilesContext';
 import ProfileNotFound from './ProfileNotFound';
@@ -36,6 +37,8 @@ export default function ProfileContainer() {
   const context = useQualityProfilesContext();
   const { profiles } = context;
 
+  const intl = useIntl();
+
   // try to find a quality profile with the given key
   // if managed to find one, redirect to a new version
   // otherwise show not found page
@@ -63,9 +66,9 @@ export default function ProfileContainer() {
       <Helmet
         defer={false}
         title={profile.name}
-        titleTemplate={translateWithParameters(
-          'page_title.template.with_category',
-          translate('quality_profiles.page'),
+        titleTemplate={intl.formatMessage(
+          { id: 'page_title.template.with_category' },
+          { page: translate('quality_profiles.page') },
         )}
       />
       <ProfileHeader
index e44730a842831784f02c4eee213120bdee3bc9af..6b1220bcf68f80591a71ffcde88d66d148c3b3d5 100644 (file)
@@ -19,8 +19,9 @@
  */
 
 import styled from '@emotion/styled';
-import { Spinner } from '@sonarsource/echoes-react';
+import { LogoSize, LogoSonar, Spinner } from '@sonarsource/echoes-react';
 import { Helmet } from 'react-helmet-async';
+import { FormattedMessage } from 'react-intl';
 import {
   Card,
   FlagMessage,
@@ -31,8 +32,8 @@ import {
   themeBorder,
   themeColor,
 } from '~design-system';
-import { Image } from '~sonar-aligned/components/common/Image';
 import { Location } from '~sonar-aligned/types/router';
+import { SonarQubeProductLogo } from '../../../components/branding/SonarQubeProductLogo';
 import { translate } from '../../../helpers/l10n';
 import { getReturnUrl } from '../../../helpers/urls';
 import { IdentityProvider } from '../../../types/types';
@@ -53,13 +54,15 @@ export default function Login(props: Readonly<LoginProps>) {
   const displayError = Boolean(location.query.authorizationError);
 
   return (
-    <div className="sw-flex sw-flex-col sw-items-center" id="login_form">
+    <div className="sw-flex sw-flex-col sw-items-center sw-pt-32" id="login_form">
       <Helmet defer={false} title={translate('login.page')} />
-      <Image alt="" className="sw-mt-32" src="/images/sonar-logo-horizontal.png" />
+      <LogoSonar hasText size={LogoSize.Large} />
       <Card className="sw-my-14 sw-p-0 sw-w-abs-350">
         <PageContentFontWrapper className="sw-typo-lg sw-flex sw-flex-col sw-items-center sw-py-8 sw-px-4">
-          <Image alt="" className="sw-mb-6" src="/images/embed-doc/sq-icon.svg" width={28} />
-          <Title className="sw-mb-6">{translate('login.login_to_sonarqube')}</Title>
+          <SonarQubeProductLogo size={LogoSize.Small} />
+          <Title className="sw-my-6 sw-text-center">
+            <FormattedMessage id="login.login_to_sonarqube" />
+          </Title>
           <Spinner isLoading={loading}>
             <>
               {displayError && (
index 530c8a9baa3e25ae27f9063ee2b05904929b9e22..6e53ac929b995ffc70675bd38ac8e1442b83a44f 100644 (file)
@@ -40,6 +40,14 @@ jest.mock('../../../helpers/l10n', () => ({
   hasMessage: jest.fn(),
 }));
 
+jest.mock('../../../helpers/l10nBundle', () => {
+  const bundle = jest.requireActual('../../../helpers/l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }),
+  };
+});
+
 const fields = [
   { key: 'foo', type: 'STRING' } as SettingFieldDefinition,
   { key: 'bar', type: 'SINGLE_SELECT_LIST' } as SettingFieldDefinition,
index f15e9e0128d700842024623ce01c41671cc54545..bd30797fb901cdf22bec44398b28d1acb02ca15b 100644 (file)
@@ -23,8 +23,10 @@ import userEvent from '@testing-library/user-event';
 import { first } from 'lodash';
 import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
 import SystemServiceMock from '../../../../api/mocks/SystemServiceMock';
+import { mockAppState } from '../../../../helpers/testMocks';
 import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
 import { AppState } from '../../../../types/appstate';
+import { EditionKey } from '../../../../types/editions';
 import routes from '../../routes';
 import { LogsLevels } from '../../utils';
 
@@ -125,7 +127,9 @@ describe('System Info Cluster', () => {
 });
 
 function renderSystemApp(appState?: AppState) {
-  return renderAppRoutes('system', routes, { appState });
+  return renderAppRoutes('system', routes, {
+    appState: mockAppState({ edition: EditionKey.developer, ...appState }),
+  });
 }
 
 function getPageObjects() {
diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx
new file mode 100644 (file)
index 0000000..11c7b83
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { useAppState } from '../../app/components/app-state/withAppStateContext';
+import { EditionKey } from '../../types/editions';
+
+interface Props {
+  className?: string;
+  connected: boolean;
+}
+
+/**
+ * This component switches between the Community and Server product versions' logo,
+ * and between the connected and requested versions of the illustration
+ */
+export function SonarQubeConnectionIllustration(props: Props) {
+  const { edition } = useAppState();
+
+  return edition === EditionKey.community ? (
+    <CommunityIllustration {...props} />
+  ) : (
+    <ServerIllustration {...props} />
+  );
+}
+
+function CommunityIllustration({ connected, ...imageProps }: Props) {
+  return connected ? (
+    <svg
+      aria-hidden
+      width="179"
+      height="98"
+      viewBox="0 0 179 98"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...imageProps}
+    >
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z"
+        fill="var(--echoes-color-icon-success)"
+      />
+      <path
+        d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <path
+        d="M155.444 63.552A1.55 1.55 0 0 1 153.892 62c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.551-1.551A1.55 1.55 0 0 1 132.995 38c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.551 1.552ZM132.995 86c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 112.099 62c0 11.524 9.372 20.897 20.896 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 132.995 86Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M147.582 63.552A1.55 1.55 0 0 1 146.03 62c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.551-1.551 1.55 1.55 0 0 1 1.551-1.552c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.551 1.552ZM132.995 78.138c-8.896 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 119.961 62c0 7.19 5.845 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552 1.55 1.55 0 0 1-1.552 1.552ZM133.016 70.286a8.254 8.254 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.254 8.254 0 0 1-5.855 2.42v.011Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z"
+        fill="var(--echoes-color-icon-success)"
+      />
+    </svg>
+  ) : (
+    <svg
+      aria-hidden
+      width="179"
+      height="98"
+      viewBox="0 0 179 98"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...imageProps}
+    >
+      <path
+        d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <path
+        d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M165.448 62.552A1.55 1.55 0 0 1 163.897 61c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 37c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM143 85c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 122.103 61c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 143 85Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M157.586 62.552A1.55 1.55 0 0 1 156.034 61c0-7.19-5.844-13.035-13.034-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 44.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM143 77.138c-8.897 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 129.966 61c0 7.19 5.844 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552A1.55 1.55 0 0 1 143 77.138ZM143.021 69.286a8.252 8.252 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431c2.213 0 4.293.858 5.855 2.43.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.387 0-2.69.537-3.662 1.52a5.187 5.187 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.256 8.256 0 0 1-5.855 2.42v.011Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+    </svg>
+  );
+}
+
+function ServerIllustration({ connected, ...imageProps }: Props) {
+  return connected ? (
+    <svg
+      aria-hidden
+      width="179"
+      height="98"
+      viewBox="0 0 179 98"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...imageProps}
+    >
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z"
+        fill="var(--echoes-color-icon-success)"
+      />
+      <path
+        d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <g clipPath="url(#sqciok)" fill="var(--echoes-logos-colors-brand)">
+        <path d="M155.268 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.814 10.682 23.814 23.812 0 .85-.69 1.54-1.54 1.54h-.001ZM132.994 85.812c-13.129 0-23.811-10.681-23.811-23.812a1.54 1.54 0 0 1 3.079 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 1 1 0 3.079ZM139.677 63.54c-.85 0-1.54-.69-1.54-1.54a5.147 5.147 0 0 0-5.142-5.141 1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.539 1.54h-.001ZM132.994 70.222c-4.532 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 1 1 0 3.079h.001Z" />
+        <path d="M147.472 63.54a1.54 1.54 0 0 1-1.539-1.54c0-7.133-5.804-12.937-12.939-12.937a1.54 1.54 0 0 1 0-3.078c8.833 0 16.018 7.185 16.018 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM132.994 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 1 1 0 3.08h.001Z" />
+      </g>
+      <path
+        d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z"
+        fill="var(--echoes-color-icon-success)"
+      />
+      <defs>
+        <clipPath id="sqciok">
+          <path fill="#fff" transform="translate(108.995 38)" d="M0 0h48v48H0z" />
+        </clipPath>
+      </defs>
+    </svg>
+  ) : (
+    <svg
+      aria-hidden
+      width="179"
+      height="98"
+      viewBox="0 0 179 98"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...imageProps}
+    >
+      <path
+        d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <path
+        d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <g clipPath="url(#sqcireq)" fill="var(--echoes-logos-colors-brand)">
+        <path d="M165.273 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54h-.001ZM142.999 85.812c-13.13 0-23.811-10.681-23.811-23.812a1.54 1.54 0 1 1 3.078 0c0 11.433 9.3 20.733 20.733 20.733a1.54 1.54 0 0 1 0 3.079ZM149.682 63.54a1.54 1.54 0 0 1-1.54-1.54A5.147 5.147 0 0 0 143 56.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM142.999 70.222c-4.533 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079h.001Z" />
+        <path d="M157.477 63.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.539 1.54v-.002ZM142.999 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08h.001Z" />
+      </g>
+      <defs>
+        <clipPath id="sqcireq">
+          <path fill="#fff" transform="translate(119 38)" d="M0 0h48v48H0z" />
+        </clipPath>
+      </defs>
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx
new file mode 100644 (file)
index 0000000..3e4ea0b
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { useAppState } from '../../app/components/app-state/withAppStateContext';
+import { EditionKey } from '../../types/editions';
+
+interface Props {
+  className?: string;
+}
+/**
+ * This component switches between the Community and Server product versions' logo
+ */
+export function SonarQubeIDEPromotionIllustration({ className }: Props) {
+  const { edition } = useAppState();
+
+  return edition === EditionKey.community ? (
+    <svg
+      aria-hidden
+      height="120"
+      viewBox="0 0 82 173"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      className={className}
+    >
+      <path
+        d="M46.924 71H1.255L17.297 1H66c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15H46.924Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M68.448 36.552A1.55 1.55 0 0 1 66.897 35c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 46 11c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM46 59c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 25.103 35c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 46 59Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M60.586 36.552A1.55 1.55 0 0 1 59.035 35c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 46 18.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM46 51.138c-8.897 0-16.138-7.241-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 32.965 35c0 7.19 5.845 13.035 13.035 13.035a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 46 51.138ZM46.02 43.286a8.255 8.255 0 0 1-5.855-2.42c-3.227-3.228-3.227-8.483 0-11.721a8.233 8.233 0 0 1 5.856-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.189 5.189 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.255 8.255 0 0 1-5.855 2.42v.011Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <path
+        d="M41.82 88.112h5.233v1.632H41.82V95h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <path
+        d="M35.076 102H80.745l-16.042 70H16c-8.284 0-15-6.716-15-15v-40c0-8.284 6.716-15 15-15h19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M56.55 139.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 158.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 123.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+    </svg>
+  ) : (
+    <svg
+      aria-hidden
+      height="120"
+      viewBox="0 0 82 173"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      className={className}
+    >
+      <path
+        d="M46.924 71H1.255L17.297 1H66c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15H46.924Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <g clipPath="url(#sqidepi)" fill="var(--echoes-logos-colors-brand)">
+        <path d="M68.273 37.54c-.85 0-1.54-.69-1.54-1.54 0-11.433-9.3-20.733-20.733-20.733a1.54 1.54 0 0 1 0-3.079c13.13 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54ZM46 59.812C32.87 59.812 22.186 49.13 22.186 36a1.54 1.54 0 0 1 3.08 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 0 1 0 3.079ZM52.682 37.54c-.85 0-1.54-.69-1.54-1.54A5.147 5.147 0 0 0 46 30.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM46 44.222c-4.534 0-8.222-3.688-8.222-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079H46Z" />
+        <path d="M60.477 37.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM46 52.017c-8.832 0-16.017-7.185-16.017-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08H46Z" />
+      </g>
+      <path
+        d="M41.82 88.112h5.233v1.632H41.82V95h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+        fill="var(--echoes-color-text-subdued)"
+      />
+      <path
+        d="M35.076 102H80.745l-16.042 70H16c-8.284 0-15-6.716-15-15v-40c0-8.284 6.716-15 15-15h19.076Z"
+        stroke="var(--echoes-color-border-weak)"
+        strokeWidth="2"
+      />
+      <path
+        d="M56.55 139.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 158.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 123.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+        fill="var(--echoes-logos-colors-brand)"
+      />
+      <defs>
+        <clipPath id="sqidepi">
+          <path fill="#fff" transform="translate(22 12)" d="M0 0h48v48H0z" />
+        </clipPath>
+      </defs>
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx
new file mode 100644 (file)
index 0000000..a9db4c2
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { LogoSonarQubeCommunity, LogoSonarQubeServer } from '@sonarsource/echoes-react';
+import * as React from 'react';
+import { useAppState } from '../../app/components/app-state/withAppStateContext';
+import { EditionKey } from '../../types/editions';
+
+type Props = React.ComponentProps<typeof LogoSonarQubeServer>;
+
+/**
+ * This component switches between the Community and Server product versions' logo
+ */
+export function SonarQubeProductLogo(props: Props) {
+  const { edition } = useAppState();
+
+  const OfficialLogo =
+    edition === EditionKey.community ? LogoSonarQubeCommunity : LogoSonarQubeServer;
+
+  return <OfficialLogo {...props} />;
+}
diff --git a/server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx b/server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx
new file mode 100644 (file)
index 0000000..b95f336
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { mockAppState } from '../../../helpers/testMocks';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { EditionKey } from '../../../types/editions';
+import { SonarQubeConnectionIllustration } from '../SonarQubeConnectionIllustration';
+
+it.each([
+  [EditionKey.community, true],
+  [EditionKey.community, false],
+  [EditionKey.enterprise, true],
+  [EditionKey.enterprise, false],
+])('should render %s edition (variant connected %s) correctly', (edition, connected) => {
+  const { container } = renderComponent(
+    <SonarQubeConnectionIllustration connected={connected} />,
+    '',
+    { appState: mockAppState({ edition }) },
+  );
+
+  expect(container).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap b/server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap
new file mode 100644 (file)
index 0000000..400a3b1
--- /dev/null
@@ -0,0 +1,189 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render community edition (variant connected false) correctly 1`] = `
+<div>
+  <svg
+    aria-hidden="true"
+    fill="none"
+    height="98"
+    viewBox="0 0 179 98"
+    width="179"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z"
+      stroke="var(--echoes-color-border-weak)"
+      stroke-width="2"
+    />
+    <path
+      d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+      fill="var(--echoes-color-text-subdued)"
+    />
+    <path
+      d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z"
+      stroke="var(--echoes-color-border-weak)"
+      stroke-width="2"
+    />
+    <path
+      d="M165.448 62.552A1.55 1.55 0 0 1 163.897 61c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 37c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM143 85c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 122.103 61c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 143 85Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M157.586 62.552A1.55 1.55 0 0 1 156.034 61c0-7.19-5.844-13.035-13.034-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 44.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM143 77.138c-8.897 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 129.966 61c0 7.19 5.844 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552A1.55 1.55 0 0 1 143 77.138ZM143.021 69.286a8.252 8.252 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431c2.213 0 4.293.858 5.855 2.43.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.387 0-2.69.537-3.662 1.52a5.187 5.187 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.256 8.256 0 0 1-5.855 2.42v.011Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+  </svg>
+</div>
+`;
+
+exports[`should render community edition (variant connected true) correctly 1`] = `
+<div>
+  <svg
+    aria-hidden="true"
+    fill="none"
+    height="98"
+    viewBox="0 0 179 98"
+    width="179"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      clip-rule="evenodd"
+      d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z"
+      fill="var(--echoes-color-icon-success)"
+      fill-rule="evenodd"
+    />
+    <path
+      d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+      fill="var(--echoes-color-text-subdued)"
+    />
+    <path
+      d="M155.444 63.552A1.55 1.55 0 0 1 153.892 62c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.551-1.551A1.55 1.55 0 0 1 132.995 38c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.551 1.552ZM132.995 86c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 112.099 62c0 11.524 9.372 20.897 20.896 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 132.995 86Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M147.582 63.552A1.55 1.55 0 0 1 146.03 62c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.551-1.551 1.55 1.55 0 0 1 1.551-1.552c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.551 1.552ZM132.995 78.138c-8.896 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 119.961 62c0 7.19 5.845 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552 1.55 1.55 0 0 1-1.552 1.552ZM133.016 70.286a8.254 8.254 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.254 8.254 0 0 1-5.855 2.42v.011Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z"
+      fill="var(--echoes-color-icon-success)"
+    />
+  </svg>
+</div>
+`;
+
+exports[`should render enterprise edition (variant connected false) correctly 1`] = `
+<div>
+  <svg
+    aria-hidden="true"
+    fill="none"
+    height="98"
+    viewBox="0 0 179 98"
+    width="179"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z"
+      stroke="var(--echoes-color-border-weak)"
+      stroke-width="2"
+    />
+    <path
+      d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+      fill="var(--echoes-color-text-subdued)"
+    />
+    <path
+      d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z"
+      stroke="var(--echoes-color-border-weak)"
+      stroke-width="2"
+    />
+    <g
+      clip-path="url(#sqcireq)"
+      fill="var(--echoes-logos-colors-brand)"
+    >
+      <path
+        d="M165.273 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54h-.001ZM142.999 85.812c-13.13 0-23.811-10.681-23.811-23.812a1.54 1.54 0 1 1 3.078 0c0 11.433 9.3 20.733 20.733 20.733a1.54 1.54 0 0 1 0 3.079ZM149.682 63.54a1.54 1.54 0 0 1-1.54-1.54A5.147 5.147 0 0 0 143 56.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM142.999 70.222c-4.533 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079h.001Z"
+      />
+      <path
+        d="M157.477 63.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.539 1.54v-.002ZM142.999 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08h.001Z"
+      />
+    </g>
+    <defs>
+      <clippath
+        id="sqcireq"
+      >
+        <path
+          d="M0 0h48v48H0z"
+          fill="#fff"
+          transform="translate(119 38)"
+        />
+      </clippath>
+    </defs>
+  </svg>
+</div>
+`;
+
+exports[`should render enterprise edition (variant connected true) correctly 1`] = `
+<div>
+  <svg
+    aria-hidden="true"
+    fill="none"
+    height="98"
+    viewBox="0 0 179 98"
+    width="179"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      clip-rule="evenodd"
+      d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z"
+      fill="var(--echoes-color-icon-success)"
+      fill-rule="evenodd"
+    />
+    <path
+      d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z"
+      fill="var(--echoes-logos-colors-brand)"
+    />
+    <path
+      d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z"
+      fill="var(--echoes-color-text-subdued)"
+    />
+    <g
+      clip-path="url(#sqciok)"
+      fill="var(--echoes-logos-colors-brand)"
+    >
+      <path
+        d="M155.268 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.814 10.682 23.814 23.812 0 .85-.69 1.54-1.54 1.54h-.001ZM132.994 85.812c-13.129 0-23.811-10.681-23.811-23.812a1.54 1.54 0 0 1 3.079 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 1 1 0 3.079ZM139.677 63.54c-.85 0-1.54-.69-1.54-1.54a5.147 5.147 0 0 0-5.142-5.141 1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.539 1.54h-.001ZM132.994 70.222c-4.532 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 1 1 0 3.079h.001Z"
+      />
+      <path
+        d="M147.472 63.54a1.54 1.54 0 0 1-1.539-1.54c0-7.133-5.804-12.937-12.939-12.937a1.54 1.54 0 0 1 0-3.078c8.833 0 16.018 7.185 16.018 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM132.994 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 1 1 0 3.08h.001Z"
+      />
+    </g>
+    <path
+      d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z"
+      fill="var(--echoes-color-icon-success)"
+    />
+    <defs>
+      <clippath
+        id="sqciok"
+      >
+        <path
+          d="M0 0h48v48H0z"
+          fill="#fff"
+          transform="translate(108.995 38)"
+        />
+      </clippath>
+    </defs>
+  </svg>
+</div>
+`;
index 5bffded6ae84d0d8028478db03b30eda950c4a24..9824cec5a8f7f0e7399764d9e1615035324ce1f0 100644 (file)
 
 import { DropdownMenu } from '@sonarsource/echoes-react';
 import * as React from 'react';
-import { Image } from '~sonar-aligned/components/common/Image';
 import { DocLink } from '../../helpers/doc-links';
 import { translate } from '../../helpers/l10n';
 import { SuggestionLink } from '../../types/types';
 import { DocItemLink } from './DocItemLink';
 import { SuggestionsContext } from './SuggestionsContext';
 
-function IconLink({
-  icon = 'embed-doc/sq-icon.svg',
-  link,
-  text,
-}: {
-  icon?: string;
-  link: string;
-  text: string;
-}) {
-  return (
-    <DropdownMenu.ItemLink
-      prefix={
-        <Image
-          alt={text}
-          aria-hidden
-          className="sw-mr-2"
-          height="18"
-          src={`/images/${icon}`}
-          width="18"
-        />
-      }
-      to={link}
-    >
-      {text}
-    </DropdownMenu.ItemLink>
-  );
-}
-
 function Suggestions({ suggestions }: Readonly<{ suggestions: SuggestionLink[] }>) {
   return (
     <>
@@ -103,21 +74,15 @@ export function EmbedDocsPopup() {
 
       <DropdownMenu.GroupLabel>{translate('docs.stay_connected')}</DropdownMenu.GroupLabel>
 
-      <IconLink
-        link="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube"
-        text={translate('docs.news')}
-      />
-
-      <IconLink
-        link="https://www.sonarsource.com/products/sonarqube/roadmap/?referrer=sonarqube"
-        text={translate('docs.roadmap')}
-      />
-
-      <IconLink
-        icon="embed-doc/x-icon-black.svg"
-        link="https://twitter.com/SonarQube"
-        text="X @SonarQube"
-      />
+      <DropdownMenu.ItemLink to="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube">
+        {translate('docs.news')}
+      </DropdownMenu.ItemLink>
+
+      <DropdownMenu.ItemLink to="https://www.sonarsource.com/products/sonarqube/roadmap/?referrer=sonarqube">
+        {translate('docs.roadmap')}
+      </DropdownMenu.ItemLink>
+
+      <DropdownMenu.ItemLink to="https://twitter.com/SonarQube">X @SonarQube</DropdownMenu.ItemLink>
     </>
   );
 }
diff --git a/server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx b/server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx
new file mode 100644 (file)
index 0000000..4971033
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { ComponentProps } from 'react';
+import { FormattedMessage } from 'react-intl';
+
+type Props = ComponentProps<typeof FormattedMessage>;
+
+export function TranslatedMessage(props: Props) {
+  return <FormattedMessage {...props} />;
+}
index 8b9452fe2fff798e88f9c4bd5c7b5cea89c3067b..bd481c9c061a10af7cdb30f35443df6dd8952997 100644 (file)
@@ -27,10 +27,11 @@ import { useDocUrl } from '../../helpers/docs';
 import { getInstanceVersionNumber } from '../../helpers/strings';
 import { isCurrentVersionEOLActive } from '../../helpers/system';
 import { useSystemUpgrades } from '../../queries/system';
+import { EditionKey } from '../../types/editions';
 
 export default function AppVersionStatus() {
   const { data } = useSystemUpgrades();
-  const { version, versionEOL } = useAppState();
+  const { edition, version, versionEOL } = useAppState();
 
   const isActiveVersion = useMemo(() => {
     if (data?.installedVersionActive !== undefined) {
@@ -47,7 +48,7 @@ export default function AppVersionStatus() {
     { id: `footer.version` },
     {
       version: getInstanceVersionNumber(version),
-      status: (
+      status: edition && edition !== EditionKey.community && (
         <LinkStandalone className="sw-ml-1" highlight={LinkHighlight.CurrentColor} to={docUrl}>
           <FormattedMessage
             id={`footer.version.status.${isActiveVersion ? 'active' : 'inactive'}`}
index 4a08ed786972600bbba8ec9c8e83624f5ef16493..75e3731f4cf989f25aaaacff3979ddb8bcc6b9c9 100644 (file)
@@ -59,7 +59,7 @@ export default function RepositoryVariables(props: RepositoryVariablesProps) {
                   almBinding,
                   projectBinding,
                 )}/admin/addon/admin/pipelines/repository-variables`}
-                target="_blank"
+                shouldOpenInNewTab
               >
                 {translate('onboarding.tutorial.with.bitbucket_pipelines.variables.intro.link')}
               </LinkStandalone>
index eaf7bdf367d51e593e0d2d76d91206610bd20d19..a4606e48efdea3532d8cb95050f3c366235339e3 100644 (file)
@@ -65,7 +65,7 @@ export default function GithubCFamilyExampleRepositories(
           height={20}
           src="/images/alm/github.svg"
         />
-        <LinkStandalone target="_blank" to={link}>
+        <LinkStandalone shouldOpenInNewTab to={link}>
           sonarsource-cfamily-examples
         </LinkStandalone>
       </div>
index acb6dfef1dbcf932dc573561e0787b7922263e1e..38f0f198ee82e6c0bc14dd7b21d6a155a3b1b99e 100644 (file)
@@ -59,7 +59,7 @@ export default function SecretStep(props: SecretStepProps) {
             almBinding && projectBinding ? (
               <LinkStandalone
                 to={`${buildGithubLink(almBinding, projectBinding)}/settings/secrets`}
-                target="_blank"
+                shouldOpenInNewTab
               >
                 {translate('onboarding.tutorial.with.github_action.secret.intro.link')}
               </LinkStandalone>
index aee023dcbeb34c249ba004fe80216ac19ea8927c..f46255e766cee79baa1feb64f18e8cb9fd1c429e 100644 (file)
@@ -71,9 +71,11 @@ export function getCommonNodes(ci: TutorialModes) {
     expiresInSelect: byRole('combobox', { name: '' }),
     tokenValue: byText('generatedtoken2'),
     linkToRepo: byRole('link', {
-      name: `onboarding.tutorial.with.${CI_TRANSLATE_MAP[ci]}.${
-        ci === TutorialModes.GitHubActions ? 'secret' : 'variables'
-      }.intro.link`,
+      name: new RegExp(
+        `onboarding.tutorial.with.${CI_TRANSLATE_MAP[ci]}.${
+          ci === TutorialModes.GitHubActions ? 'secret' : 'variables'
+        }.intro.link`,
+      ),
     }),
     allSetSentence: byText('onboarding.tutorial.ci_outro.done'),
   };
index f6ee51f06305fd03faf3504f139ce24cd0bd1f9e..d3f896774055c199bc8272cc1485d85dc0098a78 100644 (file)
@@ -50,7 +50,7 @@ export default function SystemUpgradeButton(props: Readonly<Props>) {
       <Link
         className="sw-ml-2"
         to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube"
-        target="_blank"
+        shouldOpenInNewTab
       >
         {translate('learn_more')}
       </Link>
index 39d66bb80f8aca4e8228c916ce7f73fee52d767d..e1d3df12fce183a67bfc369541a003f6608892f9 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { IntlShape } from 'react-intl';
 import { Dict } from '../../types/types';
 import {
   getLocalizedCategoryMetricName,
@@ -30,18 +31,30 @@ import {
   translate,
   translateWithParameters,
 } from '../l10n';
-import { getMessages } from '../l10nBundle';
+import { getIntl, getMessages } from '../l10nBundle';
 
 const MSG = 'my_message';
 
 jest.unmock('../l10n');
 
-jest.mock('../l10nBundle', () => ({
-  getMessages: jest.fn().mockReturnValue({}),
-}));
+jest.mock('../l10nBundle', () => {
+  const bundle = jest.requireActual('../l10nBundle');
+  return {
+    ...bundle,
+    getIntl: jest.fn().mockReturnValue({ formatMessage: jest.fn(({ id }) => `${id}`) }),
+    getMessages: jest.fn().mockReturnValue({}),
+  };
+});
+
+const resetMessages = (messages: Dict<string>) => {
+  jest.mocked(getMessages).mockReturnValue(messages);
 
-const resetMessages = (messages: Dict<string>) =>
-  (getMessages as jest.Mock).mockReturnValue(messages);
+  jest.mocked(getIntl).mockReturnValue({
+    formatMessage: jest.fn(({ id }) => {
+      return id ? (messages[id] ?? id) : `${id}`;
+    }),
+  } as unknown as IntlShape);
+};
 
 beforeEach(() => {
   resetMessages({});
@@ -82,6 +95,13 @@ describe('translate', () => {
     expect(translate('random', 'key')).toBe('random.key');
     expect(translate('composite.random', 'key')).toBe('composite.random.key');
   });
+
+  it('should fall back to the old system when intl is undefined', () => {
+    jest.mocked(getIntl).mockReturnValueOnce(undefined as unknown as IntlShape);
+    resetMessages({ exists: 'this exists' });
+
+    expect(translate('exists')).toBe('this exists');
+  });
 });
 
 describe('translateWithParameters', () => {
index 5ac785375b58970a215d4842019330cb36560ee3..930f244e8a9bafed0f786e2c158c700bd975bb03 100644 (file)
@@ -20,6 +20,7 @@
 
 import { fetchL10nBundle } from '../../api/l10n';
 import { loadL10nBundle } from '../l10nBundle';
+import { mockAppState } from '../testMocks';
 
 beforeEach(() => {
   jest.clearAllMocks();
@@ -33,9 +34,11 @@ jest.mock('../../api/l10n', () => ({
   }),
 }));
 
+const APP_STATE = mockAppState({});
+
 describe('#loadL10nBundle', () => {
   it('should fetch bundle without any timestamp', async () => {
-    await loadL10nBundle();
+    await loadL10nBundle(APP_STATE);
 
     expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
   });
@@ -44,7 +47,7 @@ describe('#loadL10nBundle', () => {
     const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } };
     (window as unknown as any).sonarQubeL10nBundle = cachedBundle;
 
-    await loadL10nBundle();
+    await loadL10nBundle(APP_STATE);
 
     expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined });
   });
@@ -53,7 +56,7 @@ describe('#loadL10nBundle', () => {
     const cachedBundle = { timestamp: 'timestamp', locale: 'de', messages: { cache: 'cache' } };
     (window as unknown as any).sonarQubeL10nBundle = cachedBundle;
 
-    await loadL10nBundle();
+    await loadL10nBundle(APP_STATE);
 
     expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: cachedBundle.timestamp });
   });
@@ -63,7 +66,7 @@ describe('#loadL10nBundle', () => {
     (fetchL10nBundle as jest.Mock).mockRejectedValueOnce({ status: 304 });
     (window as unknown as any).sonarQubeL10nBundle = cachedBundle;
 
-    const bundle = await loadL10nBundle();
+    const bundle = await loadL10nBundle(APP_STATE);
 
     expect(bundle).toEqual(
       expect.objectContaining({ locale: cachedBundle.locale, messages: cachedBundle.messages }),
index 09527fbb9466523ae763e13d66e7d3ed8442e146..4c8516d50fb083ab36283cff0531b7dfffbfbfff 100644 (file)
@@ -28,6 +28,14 @@ import {
 import { mockQualityGateStatusCondition } from '../mocks/quality-gates';
 import { mockMeasure, mockMeasureEnhanced, mockMetric } from '../testMocks';
 
+jest.mock('../l10nBundle', () => {
+  const bundle = jest.requireActual('../l10nBundle');
+  return {
+    ...bundle,
+    getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }),
+  };
+});
+
 describe('enhanceConditionWithMeasure', () => {
   it('should correctly map enhance conditions with measure data', () => {
     const measures = [
index 920a5a6c577d119f5e71d66de23e8c812a7dc019..4594e4ae63643ff63adbef0edd7367c468d0074f 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { getMessages } from './l10nBundle';
+import { getIntl, getMessages } from './l10nBundle';
 
 export function hasMessage(...keys: string[]): boolean {
   const messageKey = keys.join('.');
@@ -35,9 +35,14 @@ export function translate(...keys: string[]): string {
     console.error(`No message for: ${messageKey}`);
   }
 
-  return l10nMessages[messageKey] || messageKey;
+  const intl = getIntl();
+  // fallback to old if in extension
+  return intl ? intl.formatMessage({ id: messageKey }) : l10nMessages[messageKey];
 }
 
+/**
+ * @param messageKey @deprecated Use react-intl instead
+ */
 export function translateWithParameters(
   messageKey: string,
   ...parameters: Array<string | number>
index 4fc589c47f87b3018232b9dba1a0e2fcac0f178f..d34b1f479bbf12b8a1bf9d4be7ffa0d4a993cca4 100644 (file)
 
 import { IntlShape, createIntl, createIntlCache } from 'react-intl';
 import { fetchL10nBundle } from '../api/l10n';
+import { AppState } from '../types/appstate';
+import { EditionKey } from '../types/editions';
 import { L10nBundle, L10nBundleRequestParams } from '../types/l10nBundle';
 import { Dict } from '../types/types';
 import { toISO8601WithOffsetString } from './dates';
+import { isDefined } from './types';
 
 const DEFAULT_LOCALE = 'en';
 const DEFAULT_MESSAGES: Dict<string> = {
@@ -48,7 +51,7 @@ export function getCurrentL10nBundle() {
   return getL10nBundleFromCache();
 }
 
-export async function loadL10nBundle() {
+export async function loadL10nBundle(appState: AppState | undefined) {
   const browserLocale = getPreferredLanguage();
   const cachedBundle = getL10nBundleFromCache();
 
@@ -91,6 +94,17 @@ export async function loadL10nBundle() {
     {
       locale: effectiveLocale,
       messages,
+
+      /*
+       * This sets a default value for translations, so devs do not need to pass the {productName}
+       * value to every instance of FormattedMessage.
+       * It is a bit of a hack, abusing this config item that is normally for tag replacement only,
+       * hence the ts-expect-error tag
+       */
+      defaultRichTextElements: {
+        // @ts-expect-error
+        productName: getProductName(appState),
+      },
     },
     cache,
   );
@@ -109,3 +123,13 @@ function getL10nBundleFromCache(): L10nBundle {
 function persistL10nBundleInCache(bundle: L10nBundle) {
   (window as unknown as any).sonarQubeL10nBundle = bundle;
 }
+
+function getProductName(appState?: AppState) {
+  if (isDefined(appState?.edition)) {
+    return appState?.edition === EditionKey.community
+      ? 'SonarQube Community Build'
+      : 'SonarQube Server';
+  }
+
+  return 'SonarQube';
+}
index 223898cb1d11c10ee4ea2a52292ac397d99f0d4a..f5ca0dfd21224b4765b2024cc17d511374adccd1 100644 (file)
@@ -18,8 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { IntlShape } from 'react-intl';
 import { MetricType } from '~sonar-aligned/types/metrics';
-import { getMessages } from '../../../helpers/l10nBundle';
+import { getIntl, getMessages } from '../../../helpers/l10nBundle';
 import { Dict } from '../../../types/types';
 import { formatMeasure } from '../measures';
 
@@ -33,11 +34,19 @@ jest.unmock('../../../helpers/l10n');
 jest.mock('../../../helpers/l10nBundle', () => ({
   getCurrentLocale: jest.fn().mockReturnValue('us'),
   getMessages: jest.fn().mockReturnValue({}),
+  getIntl: jest.fn().mockReturnValue({ formatMessage: jest.fn(({ id }) => `${id}`) }),
 }));
 
-const resetMessages = (messages: Dict<string>) =>
+const resetMessages = (messages: Dict<string>) => {
   jest.mocked(getMessages).mockReturnValue(messages);
 
+  jest.mocked(getIntl).mockReturnValue({
+    formatMessage: jest.fn(({ id }) => {
+      return id ? (messages[id] ?? id) : `${id}`;
+    }),
+  } as unknown as IntlShape);
+};
+
 beforeAll(() => {
   resetMessages({
     'work_duration.x_days': '{0}d',
index 789ea5ad6ee247cf0e5393d4184d1a47f2899148..bf044c355c4478472e97e77a5618ba69188da6e7 100644 (file)
@@ -2248,7 +2248,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
+"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
   version: 7.25.6
   resolution: "@babel/runtime@npm:7.25.6"
   dependencies:
@@ -2257,6 +2257,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@babel/runtime@npm:^7.20.13":
+  version: 7.26.0
+  resolution: "@babel/runtime@npm:7.26.0"
+  dependencies:
+    regenerator-runtime: "npm:^0.14.0"
+  checksum: 10/9f4ea1c1d566c497c052d505587554e782e021e6ccd302c2ad7ae8291c8e16e3f19d4a7726fb64469e057779ea2081c28b7dbefec6d813a22f08a35712c0f699
+  languageName: node
+  linkType: hard
+
 "@babel/template@npm:^7.12.13, @babel/template@npm:^7.25.7":
   version: 7.25.7
   resolution: "@babel/template@npm:7.25.7"
@@ -3201,7 +3210,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@floating-ui/dom@npm:^1.0.0, @floating-ui/dom@npm:^1.0.1":
+"@floating-ui/dom@npm:^1.0.0":
+  version: 1.6.12
+  resolution: "@floating-ui/dom@npm:1.6.12"
+  dependencies:
+    "@floating-ui/core": "npm:^1.6.0"
+    "@floating-ui/utils": "npm:^0.2.8"
+  checksum: 10/5c8e5fdcd3843140a606ab6dc6c12ad740f44e66b898966ef877393faaede0bbe14586e1049e2c2f08856437da8847e884a2762e78275fefa65a5a9cd71e580d
+  languageName: node
+  linkType: hard
+
+"@floating-ui/dom@npm:^1.0.1":
   version: 1.6.10
   resolution: "@floating-ui/dom@npm:1.6.10"
   dependencies:
@@ -3211,29 +3230,29 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.1":
-  version: 2.1.1
-  resolution: "@floating-ui/react-dom@npm:2.1.1"
+"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.2":
+  version: 2.1.2
+  resolution: "@floating-ui/react-dom@npm:2.1.2"
   dependencies:
     "@floating-ui/dom": "npm:^1.0.0"
   peerDependencies:
     react: ">=16.8.0"
     react-dom: ">=16.8.0"
-  checksum: 10/cafabfb5dd0b25547863520b3bcf6faee7f087d0c3187a8779910a6838d496bf494f237bf1fe883bbfae1a7fcc399611ae52377b696065d8118bd7c1b9c0d253
+  checksum: 10/2a67dc8499674e42ff32c7246bded185bb0fdd492150067caf9568569557ac4756a67787421d8604b0f241e5337de10762aee270d9aeef106d078a0ff13596c4
   languageName: node
   linkType: hard
 
 "@floating-ui/react@npm:^0.26.9":
-  version: 0.26.23
-  resolution: "@floating-ui/react@npm:0.26.23"
+  version: 0.26.27
+  resolution: "@floating-ui/react@npm:0.26.27"
   dependencies:
-    "@floating-ui/react-dom": "npm:^2.1.1"
-    "@floating-ui/utils": "npm:^0.2.7"
+    "@floating-ui/react-dom": "npm:^2.1.2"
+    "@floating-ui/utils": "npm:^0.2.8"
     tabbable: "npm:^6.0.0"
   peerDependencies:
     react: ">=16.8.0"
     react-dom: ">=16.8.0"
-  checksum: 10/a2ffeb0bae72cac9e6583d9651e75e94c261a9e78ca4a5e862b7d33f2c19ae014cbe272627a0a0a5a2b526280efab17ec687d32ba02f6ce4e924bec562ae06ab
+  checksum: 10/ab6d05e5cc2c29272a6830c06dff07e8330f2b90234674f2d74ed8084659ebe3ac9472770a5f0e76007dd2ae89f9da75189d188437aea4b8ac2789afc21bb6b2
   languageName: node
   linkType: hard
 
@@ -3244,6 +3263,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@floating-ui/utils@npm:^0.2.8":
+  version: 0.2.8
+  resolution: "@floating-ui/utils@npm:0.2.8"
+  checksum: 10/3e3ea3b2de06badc4baebdf358b3dbd77ccd9474a257a6ef237277895943db2acbae756477ec64de65a2a1436d94aea3107129a1feeef6370675bf2b161c1abc
+  languageName: node
+  linkType: hard
+
 "@formatjs/ecma402-abstract@npm:2.0.0":
   version: 2.0.0
   resolution: "@formatjs/ecma402-abstract@npm:2.0.0"
@@ -3877,9 +3903,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@mantine/core@patch:@mantine/core@7.12.2#./config/patches/@mantine-core-npm-7.12.2-7aad505c0f.patch::locator=%40sonarsource%2Fechoes-react%40npm%3A0.9.0":
+"@mantine/core@patch:@mantine/core@7.12.2#./config/patches/@mantine-core-npm-7.12.2-7aad505c0f.patch::locator=%40sonarsource%2Fechoes-react%40npm%3A0.10.1":
   version: 7.12.2
-  resolution: "@mantine/core@patch:@mantine/core@npm%3A7.12.2#./config/patches/@mantine-core-npm-7.12.2-7aad505c0f.patch::version=7.12.2&hash=cab555&locator=%40sonarsource%2Fechoes-react%40npm%3A0.9.0"
+  resolution: "@mantine/core@patch:@mantine/core@npm%3A7.12.2#./config/patches/@mantine-core-npm-7.12.2-7aad505c0f.patch::version=7.12.2&hash=cab555&locator=%40sonarsource%2Fechoes-react%40npm%3A0.10.1"
   dependencies:
     "@floating-ui/react": "npm:^0.26.9"
     clsx: "npm:^2.1.1"
@@ -4970,9 +4996,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@sonarsource/echoes-react@npm:0.9.0":
-  version: 0.9.0
-  resolution: "@sonarsource/echoes-react@npm:0.9.0"
+"@sonarsource/echoes-react@npm:0.10.1":
+  version: 0.10.1
+  resolution: "@sonarsource/echoes-react@npm:0.10.1"
   dependencies:
     "@mantine/core": "patch:@mantine/core@7.12.2#./config/patches/@mantine-core-npm-7.12.2-7aad505c0f.patch"
     "@mantine/hooks": "npm:7.12.2"
@@ -4994,7 +5020,7 @@ __metadata:
     react-dom: ^18.0.0
     react-intl: ^6.0.0
     react-router-dom: ^6.0.0
-  checksum: 10/d67803889b7a1954fb5759954086f85b95a33b7e154863cddea6a4b134d30c66b59c864d3ec93b382035a9564d682c22452f5a5b9f40c83fd1cc642584af957a
+  checksum: 10/326e46fcf08468afbf9c6dc7b8f07d09d18d1634a63fd5f0335966e0cf6162fdee94f03e9e86cbb2eef6331afee284cc14d15aaa1e749078251fcd1733ea41e4
   languageName: node
   linkType: hard
 
@@ -6009,7 +6035,7 @@ __metadata:
     "@primer/octicons-react": "npm:19.11.0"
     "@react-spring/rafz": "npm:9.7.4"
     "@react-spring/web": "npm:9.7.4"
-    "@sonarsource/echoes-react": "npm:0.9.0"
+    "@sonarsource/echoes-react": "npm:0.10.1"
     "@tanstack/react-query": "npm:5.56.2"
     "@testing-library/dom": "npm:10.4.0"
     "@testing-library/jest-dom": "npm:6.5.0"
@@ -15928,9 +15954,9 @@ __metadata:
   linkType: hard
 
 "tslib@npm:^2.1.0":
-  version: 2.6.3
-  resolution: "tslib@npm:2.6.3"
-  checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c
+  version: 2.8.1
+  resolution: "tslib@npm:2.8.1"
+  checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7
   languageName: node
   linkType: hard
 
index 4a24430e76a63a8f0c90adf5f83d12013c696513..36f8eb405ebe412cd09f7744adadeb6ad63effb5 100644 (file)
@@ -528,9 +528,9 @@ qualifier.description.APP=Single-level aggregation with a technical focus and a
 # Admin notification
 #
 #------------------------------------------------------------------------------
-admin_notification.update.new_patch=There’s an update available for your SonarQube instance. Please update to make sure you benefit from the latest security and bug fixes.
-admin_notification.update.new_version=There’s a new version of SonarQube available. Upgrade to the latest active version to access new updates and features.
-admin_notification.update.current_version_inactive=You’re running a version of SonarQube that is no longer active. Please upgrade to an active version immediately.
+admin_notification.update.new_patch=There’s an update available for your {productName} instance. Please update to make sure you benefit from the latest security and bug fixes.
+admin_notification.update.new_version=There’s a new version of {productName} available. Upgrade to the latest active version to access new updates and features.
+admin_notification.update.current_version_inactive=You’re running a version of {productName} that is no longer active. Please upgrade to an active version immediately.
 
 #------------------------------------------------------------------------------
 #
@@ -564,7 +564,7 @@ event.category.All=All
 event.category.VERSION=Version
 event.category.QUALITY_GATE=Quality Gate
 event.category.QUALITY_PROFILE=Quality Profile
-event.category.SQ_UPGRADE=SonarQube upgrade
+event.category.SQ_UPGRADE={productName} upgrade
 event.category.DEFINITION_CHANGE=Definition Change
 event.category.ISSUE_DETECTION=Issue Detection
 event.category.OTHER=Other
@@ -577,7 +577,7 @@ event.definition_change.branch_added={project} {branch} added
 event.definition_change.branch_removed={project} {branch} removed
 event.definition_change.branch_replaced={project} {oldBranch} replaced with {newBranch}
 event.failed_conditions=Failed Conditions:
-event.sqUpgrade=First analysis since upgrading to SonarQube {sqVersion}
+event.sqUpgrade=First analysis since upgrading to {productName} {sqVersion}
 
 #------------------------------------------------------------------------------
 #
@@ -606,7 +606,7 @@ layout.settings.VW=Portfolio Settings
 layout.settings.SVW=Portfolio Settings
 layout.security_reports=Security Reports
 layout.nav.home_logo_alt=Logo, link to homepage
-layout.nav.home_sonarqube_logo_alt=SonarQube logo, link to homepage
+layout.nav.home_sonarqube_logo_alt={productName} logo, link to homepage
 layout.must_be_configured=This will be available once your project is configured and analyzed.
 layout.all_project_must_be_accessible=You need access to all projects within this {0} to access it.
 
@@ -716,7 +716,7 @@ project_baseline.compliance.warning.title.project=Your project new code definiti
 
 baseline.specific_analysis=Specific analysis
 baseline.specific_analysis.description=Choose an analysis as the baseline for the new code.
-baseline.specific_analysis.compliance_warning.title=Choosing the "Specific analysis" option from the SonarQube UI is not compliant with the Clean as You Code methodology
+baseline.specific_analysis.compliance_warning.title=Choosing the "Specific analysis" option from the {productName} UI is not compliant with the Clean as You Code methodology
 baseline.specific_analysis.compliance_warning.explanation=It has been deprecated. The option remains available through the Web API.
 baseline.specific_analysis.compliance_warning.link=Defining New Code
 baseline.reference_branch=Reference branch
@@ -729,7 +729,7 @@ baseline.last_analysis_before=Last analysis before
 baseline.next_analysis_notice=Changes will take effect after the next analysis
 
 baseline.reference_branch.choose=Choose a branch
-baseline.reference_branch.does_not_exist=Branch {0} could not be found in SonarQube.
+baseline.reference_branch.does_not_exist=Branch {branch} could not be found in {productName}.
 baseline.reference_branch.cannot_be_itself=A branch cannot be used as its own reference branch
 baseline.reference_branch.invalid_branch_setting=Branch {0} cannot use itself as a reference. Define a specific setting instead of using the project-level setting.
 baseline.edit_branch_setting=Edit the branch's setting
@@ -764,9 +764,9 @@ regulatory_page.available_branches_info.more_info.doc_link=related documentation
 #
 #------------------------------------------------------------------------------
 
-page_title.template.default=%s - SonarQube
-page_title.template.with_category=%s - {0} - SonarQube
-page_title.template.with_instance={0} - %s - SonarQube
+page_title.template.default=%s - {productName}
+page_title.template.with_category=%s - {page} - {productName}
+page_title.template.with_instance={project} - %s - {productName}
 overview.page=Overview
 code.page=Code
 permissions.page=Permissions
@@ -870,7 +870,7 @@ hotspots.comment.field=Comment:
 hotspots.comment.open=Comment
 hotspots.comment.submit=Comment
 hotspots.open_in_ide.success=Success. Switch to your IDE to see the security hotspot.
-hotspots.open_in_ide.failure=Unable to connect to your IDE to open the Security Hotspot. Please make sure you're running the latest version of SonarLint.
+hotspots.open_in_ide.failure=Unable to connect to your IDE to open the Security Hotspot. Please make sure you're running the latest version of SonarQube for IDE.
 
 hotspots.assignee.select_user=Select a user...
 hotspots.assignee.change_user=Click to change assignee
@@ -944,7 +944,7 @@ issue.assign.formlink=Assign
 issue.assign.to_me=to me
 issue.quick_fix=Quick fix
 issue.quick_fix_available_with_sonarlint=Quick fix available in {link}
-issue.quick_fix_available_with_sonarlint_no_link=Quick fix available in SonarLint
+issue.quick_fix_available_with_sonarlint_no_link=Quick fix available in SonarQube for IDE
 issue.comment.add_comment=Add Comment
 issue.comment.add_comment.cancel=Cancel adding comment
 issue.comment.enter_comment=Enter Comment
@@ -1479,9 +1479,9 @@ settings.state.url_not_valid={0} is not a valid URL
 settings._default=(default)
 settings.boolean.true=True
 settings.boolean.false=False
-settings.default.no_value=<no value>
-settings.default.complex_value=<complex value>
-settings.default.password=<password>
+settings.default.no_value='<no value>'
+settings.default.complex_value='<complex value>'
+settings.default.password='<password>'
 settings.reset_confirm.title=Reset Setting
 settings.reset_confirm.description=Are you sure that you want to reset this setting?
 settings.definition.reset=Reset "{0}" to default values
@@ -1520,8 +1520,8 @@ settings.projects.change_visibility_form.warning.gitlab=This will not change the
 settings.projects.change_visibility_form.submit=Change Default Visibility
 
 settings.almintegration.title=DevOps Platform Integrations
-settings.almintegration.description=DevOps Platform integrations allow SonarQube to interact with your DevOps Platform. This enables things like authentication, or providing analysis details and a Quality Gate to your Pull Requests directly in your DevOps Platform's interface.
-settings.almintegration.empty.server_base_url=You need to set the Server Base URL in General > {serverBaseUrl} to have correct links from the DevOps Platform to your SonarQube instance.
+settings.almintegration.description=DevOps Platform integrations allow {productName} to interact with your DevOps Platform. This enables things like authentication, or providing analysis details and a Quality Gate to your Pull Requests directly in your DevOps Platform's interface.
+settings.almintegration.empty.server_base_url=You need to set the Server Base URL in General > {serverBaseUrl} to have correct links from the DevOps Platform to your {productName} instance.
 settings.almintegration.empty.server_base_url.setting_link=Server Base URL
 settings.almintegration.tab.github=GitHub
 settings.almintegration.tab.bitbucket=Bitbucket
@@ -1529,13 +1529,13 @@ settings.almintegration.tab.azure=Azure DevOps
 settings.almintegration.tab.gitlab=GitLab
 settings.almintegration.github.info=You need to install a GitHub App with specific settings and permissions to enable Pull Request Decoration on your Organization or Repository. {link}
 settings.almintegration.github.additional_permission=If Quality Gate status reporting fails on private projects, you might need to add an additional permission to the GitHub App. {link}
-settings.almintegration.bitbucketcloud.info=SonarQube needs you to create an {oauth} in your Bitbucket Cloud workspace settings to report the Quality Gate status on Pull Requests. It needs to be a private consumer with {permission} permission. An OAuth callback URL is required by Bitbucket Cloud but not used by SonarQube so any URL works. {doc_link}
+settings.almintegration.bitbucketcloud.info={productName} needs you to create an {oauth} in your Bitbucket Cloud workspace settings to report the Quality Gate status on Pull Requests. It needs to be a private consumer with {permission} permission. An OAuth callback URL is required by Bitbucket Cloud but not used by {productName} so any URL works. {doc_link}
 settings.almintegration.bitbucketcloud.oauth=OAuth consumer
-settings.almintegration.empty.azure=Create your first Azure DevOps configuration to start analyzing your repositories on SonarQube.
-settings.almintegration.empty.bitbucket=Create your first Bitbucket configuration to start analyzing your repositories on SonarQube.
-settings.almintegration.empty.bitbucketcloud=Create your first Bitbucket Cloud configuration to start analyzing your repositories on SonarQube.
-settings.almintegration.empty.github=Create your first GitHub configuration to start analyzing your repositories on SonarQube.
-settings.almintegration.empty.gitlab=Create your first GitLab configuration to start analyzing your repositories on SonarQube.
+settings.almintegration.empty.azure=Create your first Azure DevOps configuration to start analyzing your repositories on {productName}.
+settings.almintegration.empty.bitbucket=Create your first Bitbucket configuration to start analyzing your repositories on {productName}.
+settings.almintegration.empty.bitbucketcloud=Create your first Bitbucket Cloud configuration to start analyzing your repositories on {productName}.
+settings.almintegration.empty.github=Create your first GitHub configuration to start analyzing your repositories on {productName}.
+settings.almintegration.empty.gitlab=Create your first GitLab configuration to start analyzing your repositories on {productName}.
 settings.almintegration.create=Create configuration
 settings.almintegration.create.tooltip=Upgrade to {link} to integrate with multiple {alm} instances.
 settings.almintegration.create.tooltip.link=Enterprise Edition
@@ -1594,11 +1594,11 @@ settings.almintegration.form.private_key.github.help=Your GitHub App's private k
 settings.almintegration.form.webhook_secret.github=Webhook Secret
 settings.almintegration.form.webhook_secret.github.help=Leave this field empty unless you are using GitHub Code Scanning Alert feature. See documentation for more details.
 settings.almintegration.form.personal_access_token=Personal Access Token
-settings.almintegration.form.personal_access_token.azure.help=SonarQube needs a {pat} to report the Quality Gate status on Pull Requests in Azure DevOps. To create this token, we recommend using a dedicated Azure DevOps account with administration permissions. The token itself needs {permission} permission. {doc_link}
+settings.almintegration.form.personal_access_token.azure.help={productName} needs a {pat} to report the Quality Gate status on Pull Requests in Azure DevOps. To create this token, we recommend using a dedicated Azure DevOps account with administration permissions. The token itself needs {permission} permission. {doc_link}
 settings.almintegration.form.personal_access_token.azure.help.url=Personal Access Token
-settings.almintegration.form.personal_access_token.gitlab.help=SonarQube needs a {pat} to report the Quality Gate status on Merge Requests in GitLab. To create this token, we recommend using a dedicated GitLab account with {permission} permission to all target projects. The token itself needs the {scope} scope. {doc_link}
+settings.almintegration.form.personal_access_token.gitlab.help={productName} needs a {pat} to report the Quality Gate status on Merge Requests in GitLab. To create this token, we recommend using a dedicated GitLab account with {permission} permission to all target projects. The token itself needs the {scope} scope. {doc_link}
 settings.almintegration.form.personal_access_token.gitlab.help.url=Personal Access Token
-settings.almintegration.form.personal_access_token.bitbucket.help=SonarQube needs a {pat} to report the Quality Gate status on Pull Requests in Bitbucket Server. To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions. The token itself needs {permission} permission. {doc_link}
+settings.almintegration.form.personal_access_token.bitbucket.help={productName} needs a {pat} to report the Quality Gate status on Pull Requests in Bitbucket Server. To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions. The token itself needs {permission} permission. {doc_link}
 settings.almintegration.form.personal_access_token.bitbucket.help.url=Personal Access Token
 settings.almintegration.form.save=Save configuration
 settings.almintegration.form.cancel=Cancel
@@ -1614,7 +1614,7 @@ settings.almintegration.feature.pr_decoration.disabled=Disabled
 settings.almintegration.feature.pr_decoration.disabled.no_branches=Upgrade to {link} to enable this feature.
 settings.almintegration.feature.pr_decoration.disabled.no_branches.link=Developer Edition
 settings.almintegration.feature.alm_repo_import.title=Import repositories from your DevOps Platform
-settings.almintegration.feature.alm_repo_import.description=Select repositories from your DevOps Platform, and import them into SonarQube.
+settings.almintegration.feature.alm_repo_import.description=Select repositories from your DevOps Platform, and import them into {productName}.
 settings.almintegration.feature.alm_repo_import.disabled=Disabled
 settings.almintegration.feature.alm_repo_import.disabled.no_url=This feature is disabled because your configured instance has no URL.
 settings.almintegration.tabs.authentication_moved=You can delegate authentication to this DevOps Platform. The relevant settings are under the {link} section.
@@ -1656,7 +1656,7 @@ settings.authentication.github.appid_x=App ID: {applicationId}
 settings.authentication.github.confirm.AUTO_PROVISIONING=Switch to automatic provisioning
 settings.authentication.github.confirm.JIT=Switch to Just-in-Time provisioning
 settings.authentication.github.confirm.insecure=Potentially insecure configuration
-settings.authentication.github.confirm.AUTO_PROVISIONING.description=Once you transition to automatic provisioning, groups, users, group memberships, and permissions on GitHub projects will be inherited from GitHub. You will no longer have the ability to edit them within SonarQube. Do you want to proceed with this change?
+settings.authentication.github.confirm.AUTO_PROVISIONING.description=Once you transition to automatic provisioning, groups, users, group memberships, and permissions on GitHub projects will be inherited from GitHub. You will no longer have the ability to edit them within {productName}. Do you want to proceed with this change?
 settings.authentication.github.confirm.JIT.description=Switching to Just-in-Time provisioning removes the automatic synchronization of users, groups, and group memberships. Users are provisioned and group memberships are updated only at user login. Are you sure?
 settings.authentication.github.provisioning_change.confirm_changes=Confirm change
 settings.authentication.github.provisioning_change.insecure_config=Please be aware that your configuration is potentially insecure because you didn't add any organization to the allowlist. If your GitHub App is public, anyone can install it and gain access to your instance.
@@ -1666,7 +1666,7 @@ settings.authentication.github.form.legacy_configured=Compatibility with GitHub
 settings.authentication.github.form.legacy_configured.link=Learn more about how to create a GitHub App configuration
 settings.authentication.github.enable_first=Enable your GitHub configuration for more provisioning options.
 settings.authentication.github.form.allowedOrganizations.name=Organizations
-settings.authentication.github.form.allowedOrganizations.description=Only members of these organizations will be able to authenticate to the server. ⚠ if not set, users from any organization where the GitHub App is installed will be able to login to this SonarQube instance.
+settings.authentication.github.form.allowedOrganizations.description=Only members of these organizations will be able to authenticate to the server. ⚠ if not set, users from any organization where the GitHub App is installed will be able to login to this {productName} instance.
 settings.authentication.github.form.apiUrl.name=The API url for a GitHub instance.
 settings.authentication.github.form.apiUrl.description=The API url for a GitHub instance. https://api.github.com/ for GitHub.com, https://github.company.com/api/v3/ when using GitHub Enterprise
 settings.authentication.github.form.applicationId.name=App ID
@@ -1678,7 +1678,7 @@ settings.authentication.github.form.clientSecret.description=Client password pro
 settings.authentication.github.form.privateKey.name=Private Key
 settings.authentication.github.form.privateKey.description=Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys. Copy and paste the whole contents of the file here.
 settings.authentication.github.form.synchronizeGroups.name=Synchronize teams as groups
-settings.authentication.github.form.synchronizeGroups.description=Synchronize GitHub team with SonarQube group memberships when users log in to SonarQube. For each GitHub team they belong to, users will be associated to a group of the same name if it exists in SonarQube.
+settings.authentication.github.form.synchronizeGroups.description=Synchronize GitHub team with {productName} group memberships when users log in to {productName}. For each GitHub team they belong to, users will be associated to a group of the same name if it exists in {productName}.
 settings.authentication.github.form.webUrl.name=The WEB url for a GitHub instance.
 settings.authentication.github.form.webUrl.description=The WEB url for a GitHub instance. https://github.com/ for GitHub.com, https://github.company.com/ when using GitHub Enterprise.
 settings.authentication.github.form.private_key.required_for_url_change=Please provide your private key again to update the API URL or the Web URL.
@@ -1687,12 +1687,12 @@ settings.authentication.github.form.provisioning_with_github_short.autoProvision
 settings.authentication.github.form.provisioning_with_github_short.jit=Just-in-Time provisioning
 settings.authentication.github.form.provisioning_with_github.description=Users, groups and permissions are automatically provisioned from your GitHub organizations. Once activated, users and groups can only be created and modified from your GitHub organizations/teams. Existing local users will be kept and can only be deactivated. {documentation}
 settings.authentication.github.form.description.doc=For more details, see {documentation}.
-settings.authentication.github.form.provisioning_at_login.description=Users and groups are synchronized only when users log in to SonarQube. {documentation}
+settings.authentication.github.form.provisioning_at_login.description=Users and groups are synchronized only when users log in to {productName}. {documentation}
 settings.authentication.github.form.provisioning.disabled=Your current edition does not support provisioning with GitHub. See the {documentation} for more information.
 settings.authentication.github.form.allowUsersToSignUp.name=Allow users to sign up
 settings.authentication.github.form.allowUsersToSignUp.description=Allow new users to authenticate. When set to disabled, only existing users will be able to authenticate to the server.
 settings.authentication.github.form.projectVisibility.name=Provision project visibility
-settings.authentication.github.form.projectVisibility.description=Change project visibility based on GitHub repository visibility. If disabled, every provisioned project will be private in SonarQube and visible only to users with explicit GitHub permissions for the corresponding repository. Changes take effect at the next synchronization.
+settings.authentication.github.form.projectVisibility.description=Change project visibility based on GitHub repository visibility. If disabled, every provisioned project will be private in {productName} and visible only to users with explicit GitHub permissions for the corresponding repository. Changes take effect at the next synchronization.
 settings.authentication.github.synchronize_now=Synchronize now
 settings.authentication.github.synchronization_finish=Synchronization is done.
 settings.authentication.github.configuration.validation.details=View details
@@ -1705,7 +1705,7 @@ settings.authentication.github.configuration.validation.details.title=Configurat
 settings.authentication.github.configuration.validation.details.valid_label=Valid
 settings.authentication.github.configuration.validation.details.invalid_label=Invalid
 settings.authentication.github.configuration.validation.details.org_not_found={0} (not found or app not installed)
-settings.authentication.github.configuration.roles_mapping.description=When synchronizing users and groups, SonarQube assigns permissions based on GitHub user and team roles. You can customize the mapping of permissions. The new mapping will take effect at the next synchronization.
+settings.authentication.github.configuration.roles_mapping.description=When synchronizing users and groups, {productName} assigns permissions based on GitHub user and team roles. You can customize the mapping of permissions. The new mapping will take effect at the next synchronization.
 settings.authentication.github.configuration.roles_mapping.save_success=GitHub roles mapping saved successfully.
 settings.authentication.github.configuration.roles_mapping.dialog.custom_roles_description=When a custom role name added here matches an existing GitHub custom role in any of your organizations, the mapping applies to all users with this custom role. If an existing GitHub custom role has no exact match in this list, the permissions of its inherited base role are mapped.
 settings.authentication.github.configuration.unsaved_changes=You have unsaved changes.
@@ -1723,9 +1723,9 @@ settings.authentication.gitlab.form.secret.name=Secret
 settings.authentication.gitlab.form.secret.description=Secret provided by GitLab when registering the application.
 settings.authentication.gitlab.form.secret.required_for_url_change=Please provide your secret again to update the URL.
 settings.authentication.gitlab.form.synchronizeGroups.name=Synchronize user groups
-settings.authentication.gitlab.form.synchronizeGroups.description=For each GitLab group they belong to, the user will be associated to a group with the same name (if it exists) in SonarQube. If enabled, the GitLab OAuth 2 application will need to provide the api scope.
+settings.authentication.gitlab.form.synchronizeGroups.description=For each GitLab group they belong to, the user will be associated to a group with the same name (if it exists) in {productName}. If enabled, the GitLab OAuth 2 application will need to provide the api scope.
 settings.authentication.gitlab.form.allowedGroups.name=Allowed groups
-settings.authentication.gitlab.form.allowedGroups.description.JIT=Only members of these groups (and sub-groups) will be allowed to authenticate. Enter the group slug as it appears in the GitLab URL, for instance `my-gitlab-group`. ⚠︎ When you turn on `Allow users to sign up`, make sure to also turn on group synchronization and provide a list of allowed groups. Otherwise, any GitLab user will be able to log in to this SonarQube instance.
+settings.authentication.gitlab.form.allowedGroups.description.JIT=Only members of these groups (and sub-groups) will be allowed to authenticate. Enter the group slug as it appears in the GitLab URL, for instance `my-gitlab-group`. ⚠︎ When you turn on `Allow users to sign up`, make sure to also turn on group synchronization and provide a list of allowed groups. Otherwise, any GitLab user will be able to log in to this {productName} instance.
 settings.authentication.gitlab.form.allowedGroups.description.AUTO_PROVISIONING=Only members of these groups (and sub-groups) will be provisioned. Please enter the group slug as it appears in the GitLab URL, for instance `my-gitlab-group`.
 settings.authentication.gitlab.form.allowUsersToSignUp.name=Allow users to sign up
 settings.authentication.gitlab.form.allowUsersToSignUp.description=Allow new users to authenticate. When set to disabled, only existing users will be able to authenticate to the server.
@@ -1734,12 +1734,12 @@ settings.authentication.gitlab.form.provisioningToken.description=Token used for
 settings.authentication.gitlab.applicationId.name=App ID: {0}
 settings.authentication.gitlab.enable_first=Enable your GitLab configuration for more provisioning options.
 settings.authentication.gitlab.provisioning_at_login=Just-in-Time user provisioning (default)
-settings.authentication.gitlab.provisioning_at_login.description=Users are synchronized only when users log in to SonarQube. {documentation}
+settings.authentication.gitlab.provisioning_at_login.description=Users are synchronized only when users log in to {productName}. {documentation}
 settings.authentication.gitlab.description.JIT.learn_more=Learn more about Just-in-Time provisioning with GitLab
 settings.authentication.gitlab.description.AUTO_PROVISIONING.learn_more=Learn more about automatic provisioning with GitLab
 settings.authentication.gitlab.confirm.AUTO_PROVISIONING=Switch to automatic provisioning
 settings.authentication.gitlab.confirm.JIT=Switch to Just-in-Time provisioning
-settings.authentication.gitlab.confirm.AUTO_PROVISIONING.description=Once you transition to automatic provisioning users, groups and permissions on GitLab projects will be inherited from GitLab. You will no longer have the ability to edit them within SonarQube. Do you want to proceed with this change?
+settings.authentication.gitlab.confirm.AUTO_PROVISIONING.description=Once you transition to automatic provisioning users, groups and permissions on GitLab projects will be inherited from GitLab. You will no longer have the ability to edit them within {productName}. Do you want to proceed with this change?
 settings.authentication.gitlab.confirm.JIT.description=Switching to Just-in-Time provisioning removes the automatic synchronization of users, groups and permissions. Users are provisioned and updated only at user login. Are you sure?
 settings.authentication.gitlab.confirm.insecure=Potentially insecure configuration
 settings.authentication.gitlab.provisioning_change.confirm_changes=Confirm change
@@ -1750,7 +1750,7 @@ settings.authentication.gitlab.form.provisioning.disabled=Your current edition d
 settings.authentication.gitlab.configuration.unsaved_changes=You have unsaved changes.
 settings.authentication.gitlab.configuration.valid.JIT=Configuration is valid for Just-in-Time provisioning.
 settings.authentication.gitlab.configuration.valid.AUTO_PROVISIONING=Configuration is valid for Automatic provisioning.
-settings.authentication.gitlab.configuration.roles_mapping.description=When synchronizing users and groups, SonarQube assigns permissions based on Gitlab user roles. You can customize the mapping of permissions. The new mapping will take effect at the next synchronization.
+settings.authentication.gitlab.configuration.roles_mapping.description=When synchronizing users and groups, {productName} assigns permissions based on Gitlab user roles. You can customize the mapping of permissions. The new mapping will take effect at the next synchronization.
 settings.authentication.gitlab.configuration.roles_mapping.save_success=GitLab roles mapping saved successfully.
 settings.authentication.gitlab.configuration.roles_mapping.dialog.custom_roles_description=When a custom role name added here matches an existing GitLab custom role in any of your groups or projects, the mapping applies to all users with this custom role. If an existing GitLab custom role has no exact match in this list, the permissions of its inherited base role are mapped.
 
@@ -1782,7 +1782,7 @@ settings.authentication.form.edit.saml=Edit SAML configuration
 settings.authentication.saml.configuration=SAML Configuration
 settings.authentication.saml.confirm.scim=Switch to automatic provisioning
 settings.authentication.saml.confirm.jit=Switch to Just-in-Time provisioning
-settings.authentication.saml.confirm.scim.description=Once you transition to automatic provisioning, groups, users and group memberships will be managed by your identity provider. You will no longer have the ability to edit them within SonarQube. Do you want to proceed with this change?
+settings.authentication.saml.confirm.scim.description=Once you transition to automatic provisioning, groups, users and group memberships will be managed by your identity provider. You will no longer have the ability to edit them within {productName}. Do you want to proceed with this change?
 settings.authentication.saml.confirm.jit.description=Switching to Just-in-Time provisioning removes all information provided while automatic provisioning through SCIM was active. These changes cannot be reverted. Are you sure?
 settings.authentication.saml.form.loading=Loading SAML configuration
 settings.authentication.saml.form.not_configured=SAML is not configured
@@ -1819,7 +1819,7 @@ settings.pr_decoration.binding.form.name=Configuration name
 settings.pr_decoration.binding.form.name.help=Each DevOps Platform instance must be configured globally first, and given a unique name. Pick the instance your project is hosted on.
 settings.pr_decoration.binding.form.monorepo=Enable monorepository support
 settings.pr_decoration.binding.form.monorepo.help=Enable this setting if your project is part of a monorepository. {doc_link}
-settings.pr_decoration.binding.form.monorepo.warning=This setting must be enabled for all SonarQube projects that are part of a monorepository.
+settings.pr_decoration.binding.form.monorepo.warning=This setting must be enabled for all {productName} projects that are part of a monorepository.
 settings.pr_decoration.binding.form.azure.project=Project name
 settings.pr_decoration.binding.form.azure.project.help=The name of the Azure DevOps project containing your repository. You can find this name on your project's Overview page.
 settings.pr_decoration.binding.form.azure.repository=Repository name
@@ -1850,7 +1850,7 @@ settings.mode.instance_conditions_from_other_mode=Some of the Quality Gates in t
 settings.mode.standard.name=Standard Experience
 settings.mode.mqr.name=Multi-Quality Rule (MQR) Mode
 settings.mode.standard.description.line1=Encompasses the traditional use of rule types such as bugs, code smells, and vulnerabilities, with a single category and severity level for each rule.
-settings.mode.standard.description.line2=This approach focuses on assigning severity to a rule and its issues based on the single software quality (for example, security, reliability or maintainability) it has the largest impact on. This is the rule categorization used in SonarQube 9.9 LTA and earlier.
+settings.mode.standard.description.line2=This approach focuses on assigning severity to a rule and its issues based on the single software quality (for example, security, reliability or maintainability) it has the largest impact on. This is the rule categorization used in {productName} 9.9 LTA and earlier.
 settings.mode.mqr.description.line1=Aims to more accurately represent the impact issues have on all software qualities. Very few issues impact only a single software quality. For instance, most vulnerabilities are also bugs. And vice versa. The MQR mode maps each rule to each of the qualities it impacts, with a separate severity rating for each quality. 
 settings.mode.mqr.description.line2=This approach focuses on ensuring the impact of an issue on all software qualities is clear, not just the most severe one.
 settings.mode.save.warning=Save changes to see them reflected in your instance
@@ -1931,7 +1931,7 @@ property.codefix.admin.promoted_section.title=Free - early access feature
 property.codefix.admin.promoted_section.content1=This no cost trial is offered to you at Sonar’s discretion. Sonar can decide to stop this trial anytime.
 property.codefix.admin.promoted_section.content2=At the end of the trial, this feature will be deactivated and your choice to “enable AI CodeFix” below will be ignored. Your organisation will not be charged.
 property.codefix.admin.serviceCheck.title=Test the AI CodeFix service
-property.codefix.admin.serviceCheck.description1=Make sure this SonarQube instance can communicate with the AI CodeFix service, which requires network connectivity to function.
+property.codefix.admin.serviceCheck.description1=Make sure this {productName} instance can communicate with the AI CodeFix service, which requires network connectivity to function.
 property.codefix.admin.serviceCheck.description2=This test is free and should only take a few seconds.
 property.codefix.admin.serviceCheck.learnMore=Read more about enabling AI CodeFix
 property.codefix.admin.serviceCheck.action=Test AI CodeFix service
@@ -1939,11 +1939,11 @@ property.codefix.admin.serviceCheck.spinner.label=Waiting for AI CodeFix service
 property.codefix.admin.serviceCheck.result.success=The AI CodeFix service responded successfully.
 property.codefix.admin.serviceCheck.result.unresponsive.message=The AI CodeFix service does not respond or is not reachable.
 property.codefix.admin.serviceCheck.result.unresponsive.causes.title=Here are some possible causes of this error:
-property.codefix.admin.serviceCheck.result.unresponsive.causes.1=The network may not be properly configured on this SonarQube instance. Please check the firewall and connectivity settings.
+property.codefix.admin.serviceCheck.result.unresponsive.causes.1=The network may not be properly configured on this {productName} instance. Please check the firewall and connectivity settings.
 property.codefix.admin.serviceCheck.result.unresponsive.causes.2=The AI CodeFix service may be down.
 property.codefix.admin.serviceCheck.result.requestError=Error checking the AI CodeFix service: 
 property.codefix.admin.serviceCheck.result.serviceError=The AI CodeFix service is reachable but returned an error. Check logs for more details.
-property.codefix.admin.serviceCheck.result.unauthorized=This SonarQube instance is not allowed to use AI CodeFix.
+property.codefix.admin.serviceCheck.result.unauthorized=This {productName} instance is not allowed to use AI CodeFix.
 property.codefix.admin.serviceCheck.result.unknown=The AI CodeFix service returned an unexpected message: 
 
 #------------------------------------------------------------------------------
@@ -2272,8 +2272,8 @@ project.info.see_more_info_on_x_locs=See more information on your {0} lines of c
 project.info.make_home.title=Use as homepage
 project.info.make_home.label=Make this project my homepage
 application.info.make_home.label=Make this application my homepage
-project.info.make_home.tooltip=This means you'll be redirected to this project whenever you log in to SonarQube or click on the top-left SonarQube logo.
-application.info.make_home.tooltip=This means you'll be redirected to this application whenever you log in to SonarQube or click on the top-left SonarQube logo.
+project.info.make_home.tooltip=This means you'll be redirected to this project whenever you log in to {productName} or click on the top-left {productName} logo.
+application.info.make_home.tooltip=This means you'll be redirected to this application whenever you log in to {productName} or click on the top-left {productName} logo.
 overview.project_key.tooltip.TRK=Your project key is a unique identifier for your project. If you are using Maven, make sure the key matches the "groupId:artifactId" format.
 overview.project_key.tooltip.APP=Your application key is a unique identifier for your application.
 project.info.ai_code_assurance.title=AI Code Assurance
@@ -2330,7 +2330,7 @@ quality_profiles.changelog.cca_only_changed=Clean Code attribute set to {newClea
 quality_profiles.changelog.impact_changed=Software impact set to {newSoftwareQuality} with severity {newSeverity}, was {oldSoftwareQuality} with severity {oldSeverity}
 quality_profiles.changelog.impact_added=Software impact {newSoftwareQuality} with severity {newSeverity} was added
 quality_profiles.changelog.impact_removed=Software impact {oldSoftwareQuality} with severity {oldSeverity} was removed
-quality_profiles.changelog.sq_upgrade=Instance upgraded to SonarQube {sqVersion}
+quality_profiles.changelog.sq_upgrade=Instance upgraded to {productName} {sqVersion}
 quality_profiles.deleted_profile=The profile {0} doesn't exist anymore
 quality_profiles.projects_for_default=Every project not specifically associated with a quality profile will be associated to this one by default.
 quality_profile.x_rules={count} rule(s)
@@ -2633,7 +2633,7 @@ coding_rules.context.others.description.second=Caution: The libraries mentioned
 coding_rules.context.others.description.do=Do use libraries that are compatible with the frameworks you are using.
 coding_rules.context.others.description.dont=Don’t blindly copy and paste the fixups into your code.
 coding_rules.context.others.title_feedback=Help us improve
-coding_rules.context.others.feedback_description_1=Let us know if the instructions we provide do not work for you. Tell us which framework you use and why our solution does not work by submitting an idea on the SonarQube productboard.
+coding_rules.context.others.feedback_description_1=Let us know if the instructions we provide do not work for you. Tell us which framework you use and why our solution does not work by submitting an idea on the {productName} productboard.
 coding_rules.context.others.feedback_description_2=We will do our best to provide you with more relevant instructions in the future.
 coding_rules.context.others.feedback_description.link=Submit an idea
 coding_rules.create=Create
@@ -2643,7 +2643,7 @@ coding_rules.custom_rule.help=Custom rules are created by administrators from te
 coding_rules.custom_rule.activation_notice=Note: parameters of a custom rule are not customizable on rule activation, only during creation/edit.
 coding_rules.custom_rule.software_quality_x={quality} software quality
 coding_rules.custom_rule.select_software_quality=Please select at least one software quality.
-coding_rules.custom_rule.removal=Only custom rules may be deleted. When a custom rule is deleted, it is not removed from the SonarQube instance. Instead its status is set to "REMOVED", allowing relevant issues to continue to be displayed properly.
+coding_rules.custom_rule.removal=Only custom rules may be deleted. When a custom rule is deleted, it is not removed from the {productName} instance. Instead its status is set to "REMOVED", allowing relevant issues to continue to be displayed properly.
 coding_rules.custom_rules=Custom Rules
 coding_rules.deactivate_in_quality_profile=Deactivate In Quality Profile
 coding_rules.deactivate_in_quality_profile_x=Deactivate In Quality Profile {0}
@@ -2906,9 +2906,9 @@ email_notification.form.security_protocol.description=Security protocol used to
 email_notification.form.from_address=From address
 email_notification.form.from_address.description=Address emails will come from.
 email_notification.form.from_name=From name
-email_notification.form.from_name.description=Name emails will come from (usually "SonarQube").
+email_notification.form.from_name.description=Name emails will come from (usually "{productName}").
 email_notification.form.subject_prefix=Subject prefix
-email_notification.form.subject_prefix.description=Prefix added to email so they can be easily recognized (usually "[SonarQube]").
+email_notification.form.subject_prefix.description=Prefix added to email so they can be easily recognized (usually "[{productName}]").
 email_notification.form.save_configuration=Save configuration
 email_notification.form.save_configuration.create_success=Email configuration saved successfully.
 email_notification.form.save_configuration.update_success=Email configuration updated successfully.
@@ -2923,7 +2923,7 @@ email_notification.test.modal_title=Test email
 email_notification.test.to_address=To
 email_notification.test.subject=Subject
 email_notification.test.message=Message
-email_notification.test.message_text=This is a test message from SonarQube.
+email_notification.test.message_text=This is a test message from {productName}.
 email_notification.test.create_test_email=Create test email
 email_notification.test.submit=Send test email
 email_notification.test.success=Your email was sent successfully
@@ -2992,7 +2992,7 @@ user.x_deleted={0} (deleted)
 user.confirm_password.no_match=Passwords do not match
 
 login.page=Log in
-login.login_to_sonarqube=Log in to SonarQube
+login.login_to_sonarqube=Log in to {productName}
 login.login_with_x=Log in with {0}
 login.more_options=Log in with credentials
 login.unauthorized_access_alert=You are not authorized to access this page. Please log in with more privileges and try again.
@@ -3875,8 +3875,8 @@ permission_templates.show_actions_for_x=Show actions for template {0}
 #
 #------------------------------------------------------------------------------
 
-promotion.sonarlint.title=Get the most out of SonarQube!
-promotion.sonarlint.content=Take advantage of the whole ecosystem by using SonarLint, a free IDE plugin that helps you find and fix issues earlier in your workflow. Connect SonarLint to SonarQube to sync rule sets and issue states.
+promotion.sonarlint.title=Get the most out of {productName}!
+promotion.sonarlint.content=Take advantage of the whole ecosystem by using SonarQube for IDE, a free IDE plugin that helps you find and fix issues earlier in your workflow. Connect SonarQube for IDE to {productName} to sync rule sets and issue states.
 
 #------------------------------------------------------------------------------
 #
@@ -3884,14 +3884,14 @@ promotion.sonarlint.content=Take advantage of the whole ecosystem by using Sonar
 #
 #------------------------------------------------------------------------------
 
-sonarlint-connection.request.title=Allow SonarLint connection?
-sonarlint-connection.request.description=SonarLint for {0} is requesting access to SonarQube.
-sonarlint-connection.request.description2=Do you allow SonarLint to connect? This will create a token and share it with SonarLint.
+sonarlint-connection.request.title=Allow SonarQube for IDE connection?
+sonarlint-connection.request.description=SonarQube for IDE for {ideName} is requesting access to {productName}.
+sonarlint-connection.request.description2=Do you allow SonarQube for IDE to connect? This will create a token and share it with SonarQube for IDE.
 sonarlint-connection.request.action=Allow connection
 
 sonarlint-connection.token-error.title=Token generation failed
-sonarlint-connection.token-error.description=SonarQube was not able to generate a token.
-sonarlint-connection.token-error.description2=Go back to your IDE and start again, or go to the {link} of your SonarQube account to create a new user token manually.
+sonarlint-connection.token-error.description={productName} was not able to generate a token.
+sonarlint-connection.token-error.description2=Go back to your IDE and start again, or go to the {link} of your {productName} account to create a new user token manually.
 sonarlint-connection.token-error.description2.link=Security section
 
 sonarlint-connection.connection-error.title=Token created
@@ -3900,16 +3900,16 @@ sonarlint-connection.connection-error.token-name=Token name
 sonarlint-connection.connection-error.token-value=Token value
 sonarlint-connection.connection-error.next-steps=Next steps
 sonarlint-connection.connection-error.step1=Copy the above token.
-sonarlint-connection.connection-error.step2=Go back to your IDE and paste the token in SonarLint.
+sonarlint-connection.connection-error.step2=Go back to your IDE and paste the token in SonarQube for IDE.
 
-sonarlint-connection.success.title=SonarLint connection is almost ready!
-sonarlint-connection.success.description=A new '{0}' token was created and sent to SonarLint in your IDE.
+sonarlint-connection.success.title=SonarQube for IDE connection is almost ready!
+sonarlint-connection.success.description=A new '{0}' token was created and sent to SonarQube for IDE in your IDE.
 sonarlint-connection.success.last-step=Last step
 sonarlint-connection.success.step=Go back to your IDE to complete the setup.
 
 sonarlint-connection.unspecified-ide=an unspecified IDE
 
-sonarlint-connected-mode-doc=documentation about SonarLint Connected Mode
+sonarlint-connected-mode-doc=documentation about SonarQube for IDE Connected Mode
 #------------------------------------------------------------------------------
 #
 # HELP
@@ -3927,7 +3927,6 @@ formatting.example.link.example=[link label](https://www.domain.com)
 #------------------------------------------------------------------------------
 keyboard_shortcuts_modal.title=Keyboard Shortcuts
 keyboard_shortcuts_modal.disable_link=Disable shortcuts
-keyboard_shortcuts_modal.description= You can use the following shortcuts when navigating within SonarCloud
 keyboard_shortcuts_modal.global= Global
 keyboard_shortcuts_modal.global.open_search_bar= Open search bar
 keyboard_shortcuts_modal.global.open_keyboard_shortcuts_modal= Open keyboard shortcuts modal
@@ -4059,8 +4058,8 @@ workspace.no_rule=The rule has been removed or never existed.
 #
 #------------------------------------------------------------------------------
 marketplace.page=Marketplace
-marketplace.page.description=Discover the additional benefits offered in SonarQube Commercial Editions
-marketplace.page.description_best_edition=This edition includes access to all the SonarQube-SonarLint Ecosystem features!
+marketplace.page.description=Discover the additional benefits offered in SonarQube Server Commercial Editions
+marketplace.page.description_best_edition=This edition includes access to all the SonarQube Server-SonarQube for IDE Ecosystem features!
 marketplace.page.you_are_running.community=You are currently running a Community Edition.
 marketplace.page.you_are_running.developer=You are currently running a Developer Edition.
 marketplace.page.you_are_running.enterprise=You are currently running an Enterprise Edition.
@@ -4159,7 +4158,7 @@ component_navigation.status.in_progress.admin.help=A background task is in progr
 component_navigation.status.in_progress_X.admin.help=The {type} is in progress.
 component_navigation.status.last_blocked_due_to_bad_license_X=Last analysis blocked due to an invalid license, which has since been corrected. Please reanalyze this {0}.
 
-component_navigation.pr_deco.error_detected_X=We've detected an issue with your configuration. Your SonarQube instance won't be able to perform any pull request decoration. {action}
+component_navigation.pr_deco.error_detected_X=We've detected an issue with your configuration. Your {productName} instance won't be able to perform any pull request decoration. {action}
 component_navigation.pr_deco.action.check_project_settings=Please check your project settings.
 component_navigation.pr_deco.action.contact_project_admin=Please contact your project administrator.
 
@@ -4243,8 +4242,8 @@ background_tasks.failing_count=Count of projects where processing of most recent
 #
 #------------------------------------------------------------------------------
 project_dump.page=Import / Export
-project_dump.page.description1=Moving a project from one SonarQube instance to another is a 3 step operation:
-project_dump.page.description2=Export the project, copy the generated dump on the target server, and finally import that dump from this page on the target SonarQube instance.
+project_dump.page.description1=Moving a project from one {productName} instance to another is a 3 step operation:
+project_dump.page.description2=Export the project, copy the generated dump on the target server, and finally import that dump from this page on the target {productName} instance.
 project_dump.page.description_without_import1=Export project issues, measures and measure history for import into an Enterprise Edition or higher instance of the same version and similar configuration. The export file will be generated to the file system. It must then be copied to the target file system for import.
 project_dump.page.description_without_import2=The export file will be generated to the file system. It must then be copied to the target file system for import.
 project_dump.refresh=Refresh
@@ -4296,7 +4295,7 @@ system.log_level.warning.short=Current logs level has performance impacts, get b
 system.log_level.info=Your selection does not affect the Search Engine.
 system.logs_level=Logs level
 system.logs_level.change=Change logs level
-system.new_version_available=A new version of SonarQube is available.
+system.new_version_available=A new version of {productName} is available.
 system.restart_does_not_reload_sonar_properties=Also note that a restart will not reload the sonar.properties file.
 system.see_whats_new=See what's new!
 system.release_notes=Release Notes
@@ -4305,7 +4304,7 @@ system.restart_in_progress=Restart in progress
 system.restart_server=Restart Server
 system.instance_restarting={instance} restart is in progress. Ongoing {link} are completing.
 system.search_nodes_title=Search Nodes
-system.see_sonarqube_downloads=See All SonarQube Downloads
+system.see_sonarqube_downloads=See All {productName} Downloads
 system.not_production_database_warning=This server ID is valid only for the embedded database, which should be considered disposable. Consider configuring an external database for long-term use prior to requesting your license.
 system.server_id=Server ID
 system.set_log_level=Set logs level
@@ -4427,11 +4426,11 @@ overview.project.main_branch_no_lines_of_code=The main branch has no lines of co
 overview.project.main_branch_empty=The main branch of this project is empty.
 overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it.
 overview.project.last_analysis.date_time=Last analysis started on {0}
-overview.project.next_steps.set_up_pr_deco_and_ci.admin=To benefit from more of SonarQube's features, {link_ci} and set up DevOps platform integration in your {link_project_settings}.
-overview.project.next_steps.set_up_pr_deco_and_ci=To benefit from more of SonarQube's features, {link_ci} and ask a project administrator to set up DevOps platform integration.
-overview.project.next_steps.set_up_pr_deco.admin=To benefit from more of SonarQube's features, set up DevOps platform integration in your {link_project_settings}.
-overview.project.next_steps.set_up_pr_deco=To benefit from more of SonarQube's features, ask a project administrator to set up DevOps platform integration.
-overview.project.next_steps.set_up_ci=To benefit from more of SonarQube's features, {link}.
+overview.project.next_steps.set_up_pr_deco_and_ci.admin=To benefit from more of {productName}'s features, {link_ci} and set up DevOps platform integration in your {link_project_settings}.
+overview.project.next_steps.set_up_pr_deco_and_ci=To benefit from more of {productName}'s features, {link_ci} and ask a project administrator to set up DevOps platform integration.
+overview.project.next_steps.set_up_pr_deco.admin=To benefit from more of {productName}'s features, set up DevOps platform integration in your {link_project_settings}.
+overview.project.next_steps.set_up_pr_deco=To benefit from more of {productName}'s features, ask a project administrator to set up DevOps platform integration.
+overview.project.next_steps.set_up_ci=To benefit from more of {productName}'s features, {link}.
 overview.project.next_steps.links.project_settings=project settings
 overview.project.next_steps.links.set_up_ci=set up analysis in your favorite CI
 overview.project.software_impact.has_rating=Software Quality {softwareQuality} has rating {rating}
@@ -4481,14 +4480,14 @@ overview.deprecated_profile=This Quality Profile uses {0} deprecated rules and s
 overview.deleted_profile={0} has been deleted since the last analysis.
 overview.link_to_x_profile_y=Go to {0} profile "{1}"
 
-overview.sonarlint_ad.header=Catch issues before they fail your Quality Gate with our IDE extension, SonarLint
+overview.sonarlint_ad.header=Catch issues before they fail your Quality Gate with our IDE extension, SonarQube for IDE
 overview.sonarlint_ad.details_1=The power of Sonar analyzers directly as you type
 overview.sonarlint_ad.details_2=No need to wait for your PR to pass all checks
 overview.sonarlint_ad.details_3=Repair flagged issues in real-time with quick fixes
 overview.sonarlint_ad.details_4=12 major IDE's supported (including key JetBrains and Microsoft IDE's
 overview.sonarlint_ad.details_5=Free forever
-overview.sonarlint_ad.learn_more=Learn more about SonarLint
-overview.sonarlint_ad.close_promotion=Close SonarLint promotion
+overview.sonarlint_ad.learn_more=Learn more about SonarQube for IDE
+overview.sonarlint_ad.close_promotion=Close SonarQube for IDE promotion
 
 overview.badges.get_badge=Badges
 overview.badges.title=Get project badges
@@ -4521,7 +4520,7 @@ overview.badges.renew=Renew Token
 overview.badges.renew.description=If your project badge security token has leaked to an unsafe environment, you can renew it:
 overview.badges.deprecated_badge_x_y=Badges displaying {0} are deprecated and will be removed in a future version. Please choose another badge for your {1}.
 
-overview.quality_profiles_update_after_sq_upgrade.message=Upgrade to SonarQube {sqVersion} has updated your Quality Profiles. Issues on your project may have been affected. {link}
+overview.quality_profiles_update_after_sq_upgrade.message=Upgrade to {productName} {sqVersion} has updated your Quality Profiles. Issues on your project may have been affected. {link}
 overview.quality_profiles_update_after_sq_upgrade.link=See more details
 
 overview.activity.variations.new_analysis=New analysis:
@@ -4544,7 +4543,7 @@ guiding.cayc_promotion.2.content.1=Your team or organization decides when a new
 guiding.cayc_promotion.3.title=Green is clean
 guiding.cayc_promotion.3.content.1=Quality Gate Status tells you if your new code is clean or not. Keep it green as often as possible, and your project will be production-ready.
 guiding.cayc_promotion.4.title=Clean at all levels
-guiding.cayc_promotion.4.content.1=With SonarLint, clean code as you write it in your {value}.
+guiding.cayc_promotion.4.content.1=With SonarQube for IDE, clean code as you write it in your {value}.
 guiding.cayc_promotion.4.content.2=When a feature is ready, analyze your {value} (commercial edition only) and make sure no issue is missed.
 guiding.cayc_promotion.4.content.3=Finally, rely on a thorough {value} analysis to ensure the new code is clean.
 guiding.replay_tour_button.1.title=Replay tour
@@ -4767,11 +4766,11 @@ new_code_definition.reference_branch.description=Choose a branch as the baseline
 new_code_definition.reference_branch.usecase=Recommended for projects using feature branches.
 new_code_definition.reference_branch.notice=The main branch will be set as the reference branch when the project is created. You will be able to choose another branch as the reference branch when your project will have more branches.
 
-new_code_definition.auto_update.branch.message=The new code definition of the following branch(es) was automatically changed on {date}, following a SonarQube upgrade, as it was exceeding the maximum value: {branchesList} {link}
+new_code_definition.auto_update.branch.message=The new code definition of the following branch(es) was automatically changed on {date}, following a {productName} upgrade, as it was exceeding the maximum value: {branchesList} {link}
 new_code_definition.auto_update.branch.list_item={branchName}: Number of days was changed from {previousDays} to {days}.
-new_code_definition.auto_update.global.message=The global new code definition was automatically changed from {previousDays} to {days} days on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link}
-new_code_definition.auto_update.ncd_page.message=The number of days was automatically changed from {previousDays} to {days} on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link}
-new_code_definition.auto_update.project.message=This project's new code definition was automatically changed from {previousDays} to {days} days on {date}, following a SonarQube upgrade, as it was exceeding the maximum value. {link}
+new_code_definition.auto_update.global.message=The global new code definition was automatically changed from {previousDays} to {days} days on {date}, following a {productName} upgrade, as it was exceeding the maximum value. {link}
+new_code_definition.auto_update.ncd_page.message=The number of days was automatically changed from {previousDays} to {days} on {date}, following a {productName} upgrade, as it was exceeding the maximum value. {link}
+new_code_definition.auto_update.project.message=This project's new code definition was automatically changed from {previousDays} to {days} days on {date}, following a {productName} upgrade, as it was exceeding the maximum value. {link}
 new_code_definition.auto_update.review_link=Review new code definition
 
 #------------------------------------------------------------------------------
@@ -4785,7 +4784,7 @@ onboarding.alm.bitbucketcloud=Bitbucket Cloud
 onboarding.alm.gitlab=GitLab
 
 onboarding.project_analysis.header=Analyze your project
-onboarding.project_analysis.description=We initialized your project on SonarQube, now it's up to you to launch analyses!
+onboarding.project_analysis.description=We initialized your project on {productName}, now it's up to you to launch analyses!
 onboarding.project_analysis.guide_to_integrate_pipelines=follow the guide to integrating with Pipelines
 
 onboarding.create_project.manual.step1=1 of 2
@@ -4793,7 +4792,7 @@ onboarding.create_project.manual.step2=2 of 2
 onboarding.create_project.manual.title=Create a local project
 onboarding.create_project.select_method=How do you want to create your project?
 onboarding.create_project.select_method.manually=Are you just testing or have an advanced use-case? Create a local project.
-onboarding.create_project.select_method.devops_platform=Do you want to benefit from all of SonarQube's features (like repository import and Pull Request decoration)?
+onboarding.create_project.select_method.devops_platform=Do you want to benefit from all of {productName}'s features (like repository import and Pull Request decoration)?
 onboarding.create_project.select_method.devops_platform_second=Create your project from your favorite DevOps platform.
 
 onboarding.create_project.select_method.no_alm_yet.admin=First, you need to set up a DevOps platform configuration.
@@ -4837,10 +4836,10 @@ onboarding.create_project.select_all_repositories=Select all available repositor
 
 onboarding.create_application.key.description=If specified, this value is used as the key instead of generating it from the name of the Application. Only letters, digits, dashes and underscores can be used.
 onboarding.create_project.pat_form.title=Grant access to your repositories
-onboarding.create_project.pat_form.help.azure=SonarQube needs a personal access token to access and list your repositories from Azure DevOps.
-onboarding.create_project.pat_form.help.bitbucket=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server.
-onboarding.create_project.pat_form.help.bitbucket_cloud=SonarQube needs an app password to access and list your repositories from Bitbucket Cloud.
-onboarding.create_project.pat_form.help.gitlab=SonarQube needs a personal access token to access and list your projects from GitLab.
+onboarding.create_project.pat_form.help.azure={productName} needs a personal access token to access and list your repositories from Azure DevOps.
+onboarding.create_project.pat_form.help.bitbucket={productName} needs a personal access token to access and list your repositories from Bitbucket Server.
+onboarding.create_project.pat_form.help.bitbucket_cloud={productName} needs an app password to access and list your repositories from Bitbucket Cloud.
+onboarding.create_project.pat_form.help.gitlab={productName} needs a personal access token to access and list your projects from GitLab.
 onboarding.create_project.pat_form.pat_required=Please enter a personal access token
 onboarding.create_project.wrong_binding_count=You must have at least 1 {alm} instance configured in order to use this method, but none were found. Either create a local project, or contact your system administrator.
 onboarding.create_project.wrong_binding_count.admin=You must have at least 1 {alm} instance configured in order to use this method. You can configure instances under {url}.
@@ -4859,22 +4858,22 @@ onboarding.create_project.pat.expired.info_message_contact=If this does not fix
 onboarding.create_project.pat_help.instructions.azure=To create a Personal Access Token on Azure, {link} and make sure to select the “Code (Read & Write)” scope.
 onboarding.create_project.pat_help.instructions.link.azure=generate a token
 
-onboarding.create_project.pat_help.instructions.gitlab=To create a Personal Access Token on GitLab, {link} by setting a name, for example “SonarQube” and selecting the “read_api” scope.
+onboarding.create_project.pat_help.instructions.gitlab=To create a Personal Access Token on GitLab, {link} by setting a name, for example “{productName}” and selecting the “read_api” scope.
 onboarding.create_project.pat_help.instructions.gitlab.link=generate a token
 
 onboarding.enter_username.instructions.bitbucket_cloud=You can find your username in your {link}
 onboarding.enter_username.instructions.bitbucket_cloud.link=BitBucket profile settings
 
-onboarding.create_project.enter_password.instructions.bitbucket_cloud=To create an app password on BitBucket, {link} by setting a name, for example “SonarQube” and selecting the “Repositories: Read” permissions.
+onboarding.create_project.enter_password.instructions.bitbucket_cloud=To create an app password on BitBucket, {link} by setting a name, for example “{productName}” and selecting the “Repositories: Read” permissions.
 onboarding.create_project.enter_password.instructions.bitbucket_cloud.link=add an app password
 
-onboarding.create_project.pat_help.instructions.bitbucket_server=To create a Personal Access Token on BitBucket Server, go to {link} and click on “Create token”. Set a name, for example “SonarQube” and select the following permissions “Projects: Read” “Repositories: Read”.
+onboarding.create_project.pat_help.instructions.bitbucket_server=To create a Personal Access Token on BitBucket Server, go to {link} and click on “Create token”. Set a name, for example “{productName}” and select the following permissions “Projects: Read” “Repositories: Read”.
 onboarding.create_project.pat_help.instructions.bitbucket_server.link=HTTP access tokens
 
-onboarding.create_project.pat_help.instructions2.bitbucket=Set a name, for example "SonarQube", and select the following permissions:
-onboarding.create_project.pat_help.instructions2.bitbucketcloud=Set a name, for example "SonarQube", and select the following permissions:
+onboarding.create_project.pat_help.instructions2.bitbucket=Set a name, for example "{productName}", and select the following permissions:
+onboarding.create_project.pat_help.instructions2.bitbucketcloud=Set a name, for example "{productName}", and select the following permissions:
 
-onboarding.create_project.pat_help.instructions2.gitlab=Set a name, for example "SonarQube", and select the following scope:
+onboarding.create_project.pat_help.instructions2.gitlab=Set a name, for example "{productName}", and select the following scope:
 onboarding.create_project.pat_help.gitlab.read_api_permission=read_api
 
 onboarding.create_project.no_bbs_projects=No projects could be fetched from Bitbucket Server. Contact your system administrator, or {link}.
@@ -4919,12 +4918,12 @@ onboarding.create_project.bitbucket.title=Bitbucket Server project onboarding
 onboarding.create_project.bitbucket.subtitle=Import projects from one of your Bitbucket server workspaces
 onboarding.create_project.bitbucket.subtitle.with_monorepo=Import projects from one of your Bitbucket server workspaces or {monorepoSetupLink}.
 onboarding.create_project.x_repositories_selected={count} {count, plural, one {repository} other {repositories}} selected
-onboarding.create_project.x_repository_created={count} {count, plural, one {repository} other {repositories}} will be created as {count, plural, one {a project} other {projects}} in SonarQube
+onboarding.create_project.x_repository_created={count} {count, plural, one {repository} other {repositories}} will be created as {count, plural, one {a project} other {projects}} in {productName}
 onboarding.create_project.please_dont_leave=If you leave the page the import could fail. Are you sure you want to leave?
 onboarding.create_project.import_in_progress={count} of {total} projects imported. Please do not close this page until the import is complete.
 
 onboarding.create_project.monorepo.title={almName} monorepo project onboarding
-onboarding.create_project.monorepo.subtitle=Create multiple SonarQube projects corresponding to the same monorepo and bound to the same repository.
+onboarding.create_project.monorepo.subtitle=Create multiple {productName} projects corresponding to the same monorepo and bound to the same repository.
 onboarding.create_project.monorepo.doc_link=Learn more and get help setting up your monorepo
 onboarding.create_project.monorepo.choose_organization_and_repository=Choose the organization and the repository
 onboarding.create_project.monorepo.choose_dop_setting=Choose the {almKey} configuration
@@ -4932,8 +4931,8 @@ onboarding.create_project.monorepo.choose_organization=Choose the organization
 onboarding.create_project.monorepo.choose_organization.placeholder=List of organizations
 onboarding.create_project.monorepo.choose_repository=Choose the repository
 onboarding.create_project.monorepo.choose_repository.placeholder=List of repositories
-onboarding.create_project.monorepo.choose_repository.no_already_bound_projects=This repository has no imported projects in SonarQube
-onboarding.create_project.monorepo.choose_repository.existing_already_bound_projects=This repository has already been imported, and it's linked to these projects in SonarQube:
+onboarding.create_project.monorepo.choose_repository.no_already_bound_projects=This repository has no imported projects in {productName}
+onboarding.create_project.monorepo.choose_repository.existing_already_bound_projects=This repository has already been imported, and it's linked to these projects in {productName}:
 onboarding.create_project.monorepo.no_orgs=We couldn't load any organizations with your key. Contact an administrator.
 onboarding.create_project.monorepo.no_orgs_admin=We couldn't load any organizations. Make sure the {almKey} App is installed in at least one organization and check the {almKey} instance configuration in the {link}.
 onboarding.create_project.monorepo.no_projects=No projects could be fetch from {almKey}. Contact your system administrator.
@@ -5073,12 +5072,12 @@ onboarding.tutorial.ci_outro.all_set.title=You're all set!
 onboarding.tutorial.ci_outro.done=And you are done!
 onboarding.tutorial.ci_outro.refresh_text=If everything is running successfully, once the analysis is complete you'll be redirected to the Overview page of your project where the new analysis results will be displayed. This can take a few minutes.
 onboarding.tutorial.ci_outro.commit=Commit and push your code to start the analysis.
-onboarding.tutorial.ci_outro.commit.why.gitlab=Each new push you make on your branches or merge requests will trigger a new analysis in SonarQube. We will decorate merge requests directly on GitLab for you.
-onboarding.tutorial.ci_outro.commit.why.github=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube. We will decorate pull requests directly on GitHub for you.
-onboarding.tutorial.ci_outro.commit.why.bitbucket=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube. We will decorate pull requests directly on Bitbucket for you.
-onboarding.tutorial.ci_outro.commit.why.bitbucketcloud=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube. We will decorate pull requests directly on Bitbucket for you.
-onboarding.tutorial.ci_outro.commit.why.azure=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube. We will decorate pull requests directly on Azure for you.
-onboarding.tutorial.ci_outro.commit.why.no_branches=Each new push you make on your main branch will trigger a new analysis in SonarQube.
+onboarding.tutorial.ci_outro.commit.why.gitlab=Each new push you make on your branches or merge requests will trigger a new analysis in {productName}. We will decorate merge requests directly on GitLab for you.
+onboarding.tutorial.ci_outro.commit.why.github=Each new push you make on your branches or pull requests will trigger a new analysis in {productName}. We will decorate pull requests directly on GitHub for you.
+onboarding.tutorial.ci_outro.commit.why.bitbucket=Each new push you make on your branches or pull requests will trigger a new analysis in {productName}. We will decorate pull requests directly on Bitbucket for you.
+onboarding.tutorial.ci_outro.commit.why.bitbucketcloud=Each new push you make on your branches or pull requests will trigger a new analysis in {productName}. We will decorate pull requests directly on Bitbucket for you.
+onboarding.tutorial.ci_outro.commit.why.azure=Each new push you make on your branches or pull requests will trigger a new analysis in {productName}. We will decorate pull requests directly on Azure for you.
+onboarding.tutorial.ci_outro.commit.why.no_branches=Each new push you make on your main branch will trigger a new analysis in {productName}.
 onboarding.tutorial.ci_outro.refresh=This page will then refresh with your analysis results.
 onboarding.tutorial.ci_outro.refresh.why=If the page doesn't refresh after a while, please double-check the analysis configuration, and check your logs.
 onboarding.tutorial.other.project_key.sentence=Create a {file} file in your repository and paste the following code:
@@ -5086,7 +5085,7 @@ onboarding.tutorial.other.project_key.monorepo.sentence=Create a {file} file at
 onboarding.tutorial.other.project_key.monorepo.info=Repeat this step for all the projects in your monorepo
 onboarding.tutorial.cfamilly.compilation_database_info=If you have trouble using the Build Wrapper, you can try one of the alternative methods to {link}.
 onboarding.tutorial.cfamilly.compilation_database_info.link=generate a compilation database
-onboarding.tutorial.cfamily.examples_repositories_description=Check out our C and C++ sample projects with SonarQube analysis configured
+onboarding.tutorial.cfamily.examples_repositories_description=Check out our C and C++ sample projects with {productName} analysis configured
 
 
 onboarding.tutorial.page.title=Analysis Method
@@ -5110,7 +5109,7 @@ onboarding.tutorial.breadcrumbs.gitlab-ci=GitLab CI
 onboarding.tutorial.breadcrumbs.azure-pipelines=Azure Pipelines
 onboarding.tutorial.breadcrumbs.bitbucket-pipelines=Bitbucket Pipelines
 onboarding.mode.help.manual=Use this for testing or advanced use-case. Other modes are recommended to help you set up your CI environment.
-onboarding.mode.help.otherci=SonarQube integrates with your workflow no matter which CI tool you're using.
+onboarding.mode.help.otherci={productName} integrates with your workflow no matter which CI tool you're using.
 
 
 onboarding.tutorial.with.yaml.gradle=Update your {groovy} or {kotlin} file with the {sq} plugin and its configuration:
@@ -5158,7 +5157,7 @@ onboarding.tutorial.with.gitlab_ci.project_key.objectivec.step2=Create a {file}
 
 onboarding.tutorial.with.gitlab_ci.variables.title=Add environment variables
 onboarding.tutorial.with.gitlab_ci.variables.description.link=Settings > CI/CD > Variables
-onboarding.tutorial.with.gitlab_ci.variables.section.title=Define the SonarQube Token environment variable.
+onboarding.tutorial.with.gitlab_ci.variables.section.title=Define the {productName} Token environment variable.
 onboarding.tutorial.with.gitlab_ci.variables.section.description=In GitLab, go to {link} to add the following variable and make sure it is available for your project:
 onboarding.tutorial.with.gitlab_ci.variables.edit.token.tooltip=Use an existing token or generate a new one.
 onboarding.tutorial.with.gitlab_ci.variables.step1=Key {value} {extra}
@@ -5167,13 +5166,13 @@ onboarding.tutorial.with.gitlab_ci.variables.step3=Uncheck the {value} checkbox.
 onboarding.tutorial.with.gitlab_ci.variables.step3.value=Protect Variable
 onboarding.tutorial.with.gitlab_ci.variables.section.step4=Check the {value} checkbox.
 onboarding.tutorial.with.gitlab_ci.variables.section.step4.value=Mask Variable
-onboarding.tutorial.with.gitlab_ci.variables.section2.title=Define the SonarQube URL environment variable.
+onboarding.tutorial.with.gitlab_ci.variables.section2.title=Define the {productName} URL environment variable.
 onboarding.tutorial.with.gitlab_ci.variables.section2.description=Still in {link} add a new variable and make sure it is available for your project:
 onboarding.tutorial.with.gitlab_ci.variables.section2.step4=Leave the {value} checkbox unchecked.
 onboarding.tutorial.with.gitlab_ci.yaml.title=Create or update the configuration file
 onboarding.tutorial.with.gitlab_ci.yaml.description=Create or update your {filename} file with the following content.
 onboarding.tutorial.with.gitlab_ci.yaml.filename=.gitlab-ci.yml
-onboarding.tutorial.with.gitlab_ci.yaml.baseconfig=Note that this is a minimal base configuration to run a SonarQube analysis on your main branch and merge requests, and fetch the vulnerability report (if applicable).
+onboarding.tutorial.with.gitlab_ci.yaml.baseconfig=Note that this is a minimal base configuration to run a {productName} analysis on your main branch and merge requests, and fetch the vulnerability report (if applicable).
 onboarding.tutorial.with.gitlab_ci.yaml.existing=If you already have a pipeline configured and running, you might want to add the example above to your existing yml file.
 onboarding.tutorial.with.gitlab_ci.yaml.premium=GitLab vulnerability report is only available with GitLab Ultimate. You may safely remove the sonarqube-vulnerability-report stage if you have not subscribed to this service.
 
@@ -5188,7 +5187,7 @@ onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source.bitbucketcloud=Bi
 onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source.github=GitHub Branch Source plugin for Jenkins - version 2.7.1 or later
 onboarding.tutorial.with.jenkins.prereqs.plugins.branch_source.gitlab=GitLab Branch Source plugin for Jenkins - version 1.5.3 or later
 onboarding.tutorial.with.jenkins.prereqs.plugins.gitlab_plugin=GitLab plugin for Jenkins - version 1.5.13 or later
-onboarding.tutorial.with.jenkins.prereqs.plugins.sonar_scanner=SonarQube Scanner plugin for Jenkins - version 2.11 or later
+onboarding.tutorial.with.jenkins.prereqs.plugins.sonar_scanner={productName} Scanner plugin for Jenkins - version 2.11 or later
 onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide=For a step by step guide on installing and configuring those plugins in Jenkins, visit the {link} documentation page.
 onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link=Analysis Prerequisites
 onboarding.tutorial.with.jenkins.prereqs.following_are_recommendations=We recommend using the configuration in the following steps for the best results, but you can customize it as needed.
@@ -5406,13 +5405,13 @@ onboarding.tutorial.with.jenkins.dotnet.scanner.prereqs.step5.sentence=Under {in
 onboarding.tutorial.with.jenkins.dotnet.scanner.prereqs.step5.sentence.install_from=Install from GitHub
 onboarding.tutorial.with.jenkins.dotnet.scanner.prereqs.step5.sentence.install_auto=Install automatically
 
-onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence=Make sure to replace {default} with the name you gave to your SonarQube Scanner tool {in_jenkins}.
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence=Make sure to replace {default} with the name you gave to your {productName} Scanner tool {in_jenkins}.
 onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence.default=SonarScanner
 onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.replace.sentence.in_jenkins=in Jenkins
-onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help1.sentence=To get the name of your SonarQube Scanner tool in Jenkins, navigate to {path}.
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help1.sentence=To get the name of your {productName} Scanner tool in Jenkins, navigate to {path}.
 onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help1.sentence.path=Manage Jenkins > Global Tool Configuration
 onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence=The name is located under the {path} section, in the {name} field.
-onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence.path=SonarQube Scanner > SonarQube Scanner installations
+onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence.path={productName} Scanner > {productName} Scanner installations
 onboarding.tutorial.with.jenkins.jenkinsfile.other.step3.help2.sentence.name=Name
 onboarding.tutorial.with.jenkins.jenkinsfile.cfamilly.agent_setup=We assume the Jenkins agent has the necessary tools to build your project.
 
@@ -5420,16 +5419,16 @@ onboarding.tutorial.with.jenkins.jenkinsfile.cfamilly.agent_setup=We assume the
 onboarding.tutorial.with.azure_pipelines.os=What is your agent host?
 onboarding.tutorial.with.azure_pipelines.architecture=What is your agent architecture?
 onboarding.tutorial.with.azure_pipelines.title=Analyze your project with Azure DevOps Pipelines
-onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title=Install SonarQube extension for Azure DevOps
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title=Install {productName} extension for Azure DevOps
 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence=From your Azure DevOps instance, navigate to the Visual Studio Marketplace and install the {link} by clicking the {button} button.
-onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.link=SonarQube extension
+onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.link={productName} extension
 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.sentence.button=Get it free
-onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title=Add a new SonarQube Service Endpoint in your project
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title=Add a new {productName} Service Endpoint in your project
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1.sentence=In Azure DevOps, go to {menu}.
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step1.sentence.menu=Project settings > Service connections
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2.sentence=Add a new service connection of type {type}.
-onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2.sentence.type=SonarQube
-onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step3.sentence=Enter your SonarQube server url: {url} {button}
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step2.sentence.type={productName}
+onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step3.sentence=Enter your {productName} server url: {url} {button}
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence=Enter an existing token, or a newly generated one
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step5.sentence=Enter a memorable connection name.
 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step6.sentence=Create the service connection.
@@ -5457,7 +5456,7 @@ onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.pipelin
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.task=Prepare Analysis Configuration
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.sentence.before=before
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint.sentence=Select the {endpoint} you created in Step 2.
-onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint.sentence.endpoint=SonarQube server endpoint
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.endpoint.sentence.endpoint={productName} server endpoint
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis=Under {section}, select {run_analysis_value}.
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.section=Choose the way to run the analysis
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.prepare.run_analysis.values.dotnet=Integrate with MSBuild
@@ -5486,11 +5485,11 @@ onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java_installer.pre-insta
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java=Edit or add a new {0} task:
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence=Under {section}, check {option}
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence.section=Code Analysis
-onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence.option=Run SonarQube or SonarCloud Analysis
-onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.sentence=Add a new {task} task to publish SonarQube's Quality Gate results on your build pipeline summary.
-onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1=This task may increase your build time as your pipeline will have to wait for SonarQube to process the analysis report. It is highly recommended but optional.
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.java.settings.sentence.option=Run {productName} Analysis
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.sentence=Add a new {task} task to publish {productName}'s Quality Gate results on your build pipeline summary.
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.info.sentence1=This task may increase your build time as your pipeline will have to wait for {productName} to process the analysis report. It is highly recommended but optional.
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.publish_qg.sentence.task=Publish Quality Gate Result
-onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence=Under the {tab} tab of your pipeline, check {continuous_integration} and select all the branches for which you want the SonarQube analysis to run automatically.
+onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence=Under the {tab} tab of your pipeline, check {continuous_integration} and select all the branches for which you want the {productName} analysis to run automatically.
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence.tab=Triggers
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.sentence.continuous_integration=Enable continuous integration
 onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.no_branches.sentence=Under the {tab} tab of your pipeline, check {continuous_integration} and select the main branch
@@ -5524,13 +5523,13 @@ branch_like_navigation.orphan_pull_requests=Orphan Pull Requests
 branch_like_navigation.orphan_pull_requests.tooltip=When the base of a Pull Request is deleted, this Pull Request becomes orphan.
 branch_like_navigation.for_merge_into_x_from_y=for merge into {target} from {branch}
 branch_like_navigation.for_merge_into_x_from_y.title=for merge into {0} from {1}
-branch_like_navigation.no_branch_support.title=Get the most out of SonarQube with branch and PR/MR analysis
-branch_like_navigation.no_branch_support.title.pr=Get the most out of SonarQube with branch and PR analysis
-branch_like_navigation.no_branch_support.title.mr=Get the most out of SonarQube with branch and MR analysis
-branch_like_navigation.no_branch_support.content=With Developer Edition you can analyze every pull/merge request. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
-branch_like_navigation.no_branch_support.content_x.pr=With Developer Edition you can analyze every pull request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
-branch_like_navigation.no_branch_support.content_x.mr=With Developer Edition you can analyze every merge request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
-branch_like_navigation.only_one_branch.title=Learn how to analyze branches in SonarQube
+branch_like_navigation.no_branch_support.title=Get the most out of {productName} with branch and PR/MR analysis
+branch_like_navigation.no_branch_support.title.pr=Get the most out of {productName} with branch and PR analysis
+branch_like_navigation.no_branch_support.title.mr=Get the most out of {productName} with branch and MR analysis
+branch_like_navigation.no_branch_support.content=With Developer Edition you can analyze every pull/merge request. You can also watch the quality of your release branches by analyzing each one individually in {productName}.
+branch_like_navigation.no_branch_support.content_x.pr=With Developer Edition you can analyze every pull request and get the results in {alm}. You can also watch the quality of your release branches by analyzing each one individually in {productName}.
+branch_like_navigation.no_branch_support.content_x.mr=With Developer Edition you can analyze every merge request and get the results in {alm}. You can also watch the quality of your release branches by analyzing each one individually in {productName}.
+branch_like_navigation.only_one_branch.title=Learn how to analyze branches in {productName}
 branch_like_navigation.only_one_branch.content=Quickly set up branch analysis and get separate insights for each of your branches and Pull Requests.
 branch_like_navigation.only_one_branch.documentation=Branches documentation
 branch_like_navigation.only_one_branch.pr_analysis=Pull Request analysis
@@ -5638,8 +5637,8 @@ users.login_start_with_letter_or_number=Login should start with _ or alphanumeri
 users.email=Email
 users.email.invalid=Invalid email
 users.last_connection=Last connection
-users.last_sonarlint_connection=Last SonarLint connection
-users.last_sonarlint_connection.help_text=The time of the last connection from SonarLint indicates that the user used SonarLint in connected mode.
+users.last_sonarlint_connection=Last SonarQube for IDE connection
+users.last_sonarlint_connection.help_text=The time of the last connection from SonarQube for IDE indicates that the user used SonarQube for IDE in connected mode.
 users.update_users_groups=Update {0}'s group membership
 users.view_users_groups=View {0}'s group membership
 users.update_groups=Update Groups
@@ -5654,11 +5653,11 @@ users.remove=Remove user
 users.search_description=Search users by login or name
 users.activity_filter.label=Filter users by activity
 users.activity_filter.placeholder=All users
-users.activity_filter.helptext.sonarqube=Users are considered active if they connected to SonarQube at least once in the past 30 days.
-users.activity_filter.helptext.sonarlint=Users are considered active with SonarLint if they used SonarLint in connected mode at least once in the past 30 days.
+users.activity_filter.helptext.sonarqube=Users are considered active if they connected to {productName} at least once in the past 30 days.
+users.activity_filter.helptext.sonarlint=Users are considered active with SonarQube for IDE if they used SonarQube for IDE in connected mode at least once in the past 30 days.
 users.activity_filter.all_users=All users
-users.activity_filter.active_sonarlint_users=Active users with SonarLint
-users.activity_filter.active_sonarqube_users=Active users without SonarLint
+users.activity_filter.active_sonarlint_users=Active users with SonarQube for IDE
+users.activity_filter.active_sonarqube_users=Active users without SonarQube for IDE
 users.activity_filter.inactive_users=Inactive users
 users.tokens=Tokens
 users.user_X_tokens=Tokens of {user}
@@ -5691,12 +5690,12 @@ users.tokens.new_token_created=New token "{0}" has been created. Make sure you c
 users.generate_new_token=Generate New Token
 users.new_token=New token value
 users.change_admin_password.page=Change password
-users.change_admin_password.instance_is_at_risk=Secure your SonarQube instance
+users.change_admin_password.instance_is_at_risk=Secure your {productName} instance
 users.change_admin_password.header=Default Administrator credentials are still used
-users.change_admin_password.description=Your SonarQube instance is still using default administrator credentials. You must change the password for the 'admin' account to secure your SonarQube instance.
+users.change_admin_password.description=Your {productName} instance is still using default administrator credentials. You must change the password for the 'admin' account to secure your {productName} instance.
 users.change_admin_password.form.header=Change the password for user 'admin'
 users.change_admin_password.form.success=The admin user's password was successfully changed.
-users.change_admin_password.form.continue_to_app=Continue to SonarQube
+users.change_admin_password.form.continue_to_app=Continue to {productName}
 users.filter.by=Filter by
 
 #------------------------------------------------------------------------------
@@ -5734,7 +5733,7 @@ maintenance.migration_not_supported.text=Migration is not supported on embedded
 maintenance.upgrade_database=Upgrade Database
 maintenance.upgrade_database.1=The database upgrade can take several minutes.
 maintenance.upgrade_database.2=It is mandatory to back up database before upgrading.
-maintenance.upgrade_database.3=Make sure you have followed the steps from the SonarQube Upgrade guide.
+maintenance.upgrade_database.3=Make sure you have followed the steps from the {productName} Upgrade guide.
 maintenance.upgrade=Upgrade
 maintenance.database_migration=Database Migration
 maintenance.database_is_up_to_date=Database is up-to-date
@@ -5743,14 +5742,14 @@ maintenance.sonarqube_is_down.text=Something went wrong. Please contact your sys
 maintenance.try_again=Try Again
 maintenance.is_under_maintenance={instance} is under maintenance
 maintenance.sonarqube_is_under_maintenance.1=While waiting, install {link} in your IDE!
-maintenance.sonarqube_is_under_maintenance_link.1=SonarLint
+maintenance.sonarqube_is_under_maintenance_link.1=SonarQube for IDE
 maintenance.sonarqube_is_under_maintenance.2=If you are an administrator and have no idea why this message is being shown, you should read the {link}.
 maintenance.sonarqube_is_under_maintenance_link.2=upgrade guide
 maintenance.is_starting={instance} is starting
 maintenance.is_up={instance} is up
 maintenance.all_systems_opetational=All systems operational.
 maintenance.is_offline={instance} is offline
-maintenance.sonarqube_is_offline.text=The connection to SonarQube is lost. Please contact your system administrator.
+maintenance.sonarqube_is_offline.text=The connection to {productName} is lost. Please contact your system administrator.
 maintenance.running.progress={completed} migrations completed out of {total}
 maintenance.running.estimate=Estimated completion time: {date}
 
@@ -5767,13 +5766,13 @@ indexation.progression={0} out of {1} projects reindexed.
 indexation.progression_with_error={count} out of {total} projects reindexed with some {link}.
 indexation.progression_with_error.link=tasks failing
 indexation.completed=All project data has been reloaded.
-indexation.completed_with_error=SonarQube completed the reload of project data. Some {link} causing some projects to remain unavailable.
+indexation.completed_with_error={productName} completed the reload of project data. Some {link} causing some projects to remain unavailable.
 indexation.completed_with_error.link=tasks failed or canceled
 indexation.admin_link=See {link} for more information.
 indexation.page_unavailable.title.issues=Issues page is temporarily unavailable
 indexation.page_unavailable.title.portfolios=Portfolios page is temporarily unavailable
 indexation.page_unavailable.title={componentQualifier} {componentName} is temporarily unavailable
-indexation.page_unavailable.description=SonarQube is reindexing project data.
+indexation.page_unavailable.description={productName} is reindexing project data.
 indexation.page_unavailable.description.additional_information=This page is unavailable until this process is complete. {link}
 indexation.filter_unavailable.description=This filter is unavailable until this process is complete.
 indexation.learn_more=Learn more: