diff --git a/e2e-tests/package.json b/e2e-tests/package.json index b695311267..7bf9fed112 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -19,6 +19,7 @@ "showcase-upgrade-nightly": "playwright test --project=showcase-upgrade", "showcase-auth-providers": "playwright test --project=showcase-auth-providers", "showcase-sanity-plugins": "playwright test --project=showcase-sanity-plugins", + "showcase-localization-fr": "LOCALE=fr playwright test --project=showcase-localization-fr", "lint:check": "eslint . --ext .js,.ts", "lint:fix": "eslint . \"playwright/**/*.{ts,js}\" --fix", "postinstall": "playwright install chromium", diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts index 12ccd02600..493cb8ba53 100644 --- a/e2e-tests/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -3,6 +3,15 @@ import { defineConfig, devices } from "@playwright/test"; process.env.JOB_NAME = process.env.JOB_NAME || ""; process.env.IS_OPENSHIFT = process.env.IS_OPENSHIFT || ""; +// Set LOCALE based on which project is being run +const args = process.argv; + +if (args.some((arg) => arg.includes("showcase-localization-fr"))) { + process.env.LOCALE = "fr"; +} else if (!process.env.LOCALE) { + process.env.LOCALE = "en"; +} + const k8sSpecificConfig = { use: { actionTimeout: 15 * 1000, @@ -182,5 +191,19 @@ export default defineConfig({ "**/playwright/e2e/plugins/quick-access-and-tech-radar.spec.ts", ], }, + { + name: "showcase-localization-fr", + use: { + locale: "fr", + }, + testMatch: [ + "**/playwright/e2e/extensions.spec.ts", + "**/playwright/e2e/default-global-header.spec.ts", + "**/playwright/e2e/catalog-timestamp.spec.ts", + "**/playwright/e2e/custom-theme.spec.ts", + "**/playwright/e2e/plugins/frontend/sidebar.spec.ts", + "**/playwright/e2e/settings.spec.ts", + ], + }, ], }); diff --git a/e2e-tests/playwright/e2e/catalog-timestamp.spec.ts b/e2e-tests/playwright/e2e/catalog-timestamp.spec.ts index 73ca3449a9..7f4cdecc1e 100644 --- a/e2e-tests/playwright/e2e/catalog-timestamp.spec.ts +++ b/e2e-tests/playwright/e2e/catalog-timestamp.spec.ts @@ -3,6 +3,13 @@ import { UIhelper } from "../utils/ui-helper"; import { Common, setupBrowser } from "../utils/common"; import { CatalogImport } from "../support/pages/catalog-import"; import { UI_HELPER_ELEMENTS } from "../support/page-objects/global-obj"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); let page: Page; test.describe("Test timestamp column on Catalog", () => { @@ -31,19 +38,26 @@ test.describe("Test timestamp column on Catalog", () => { }); test.beforeEach(async () => { - await uiHelper.openSidebar("Catalog"); - await uiHelper.verifyHeading("My Org Catalog"); + await uiHelper.openSidebar(t["rhdh"][lang]["menuItem.catalog"]); + await uiHelper.verifyHeading( + t["catalog"][lang]["indexPage.title"].replace("{{orgName}}", "My Org"), + ); await uiHelper.openCatalogSidebar("Component"); }); test("Import an existing Git repository and verify `Created At` column and value in the Catalog Page", async () => { - await uiHelper.clickButton("Self-service"); - await uiHelper.clickButton("Import an existing Git repository"); + await uiHelper.clickButton(t["rhdh"][lang]["menuItem.selfService"]); + await uiHelper.clickButton( + t["catalog-import-test"][lang]["buttons.importExistingGitRepository"], + ); await catalogImport.registerExistingComponent(component); await uiHelper.openCatalogSidebar("Component"); await uiHelper.searchInputPlaceholder("timestamp-test-created"); await uiHelper.verifyText("timestamp-test-created"); - await uiHelper.verifyColumnHeading(["Created At"], true); + await uiHelper.verifyColumnHeading( + [t["rhdh"][lang]["app.table.createdAt"]], + true, + ); await uiHelper.verifyRowInTableByUniqueText("timestamp-test-created", [ /^\d{1,2}\/\d{1,2}\/\d{1,4}, \d:\d{1,2}:\d{1,2} (AM|PM)$/g, ]); diff --git a/e2e-tests/playwright/e2e/custom-theme.spec.ts b/e2e-tests/playwright/e2e/custom-theme.spec.ts index c954d8ca2e..b80351b866 100644 --- a/e2e-tests/playwright/e2e/custom-theme.spec.ts +++ b/e2e-tests/playwright/e2e/custom-theme.spec.ts @@ -6,6 +6,13 @@ import { CUSTOM_SIDEBAR_LOGO, } from "../support/test-data/custom-theme"; import { ThemeConstants } from "../data/theme-constants"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); let page: Page; test.describe("CustomTheme should be applied", () => { @@ -23,7 +30,11 @@ test.describe("CustomTheme should be applied", () => { themeVerifier = new ThemeVerifier(page); await common.loginAsGuest(); - await page.getByRole("button", { name: "Hide" }).click(); + await page + .getByRole("button", { + name: t["plugin.quickstart"][lang]["footer.hide"], + }) + .click(); }); test("Verify theme colors are applied and make screenshots", async ({}, testInfo: TestInfo) => { @@ -52,14 +63,18 @@ test.describe("CustomTheme should be applied", () => { }); test("Verify that RHDH CompanyLogo can be customized", async () => { - await themeVerifier.setTheme("Light"); + await themeVerifier.setTheme( + t["user-settings"][lang]["themeToggle.names.light"], + ); await expect(page.getByTestId("home-logo")).toHaveAttribute( "src", CUSTOM_SIDEBAR_LOGO.LIGHT, ); - await themeVerifier.setTheme("Dark"); + await themeVerifier.setTheme( + t["user-settings"][lang]["themeToggle.names.dark"], + ); await expect(page.getByTestId("home-logo")).toHaveAttribute( "src", CUSTOM_SIDEBAR_LOGO.DARK, diff --git a/e2e-tests/playwright/e2e/default-global-header.spec.ts b/e2e-tests/playwright/e2e/default-global-header.spec.ts index e37e391056..e448769b69 100644 --- a/e2e-tests/playwright/e2e/default-global-header.spec.ts +++ b/e2e-tests/playwright/e2e/default-global-header.spec.ts @@ -1,6 +1,13 @@ import { expect, test } from "@playwright/test"; import { UIhelper } from "../utils/ui-helper"; import { Common } from "../utils/common"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); test.describe("Default Global Header", () => { let common: Common; @@ -26,12 +33,20 @@ test.describe("Default Global Header", () => { test("Verify that global header and default header components are visible", async ({ page, }) => { - await expect(page.locator(`input[placeholder="Search..."]`)).toBeVisible(); - await uiHelper.verifyLink({ label: "Self-service" }); + await expect( + page.locator( + `input[placeholder="${t["plugin.global-header"][lang]["search.placeholder"]}"]`, + ), + ).toBeVisible(); + await uiHelper.verifyLink({ + label: t["rhdh"][lang]["menuItem.selfService"], + }); const globalHeader = page.locator("nav[id='global-header']"); const helpDropdownButton = globalHeader - .locator("button[aria-label='Help']") + .locator( + `button[aria-label='${t["plugin.global-header"][lang]["help.tooltip"]}']`, + ) .or( globalHeader.locator("button").filter({ has: page.locator("svg[data-testid='HelpOutlineIcon']"), @@ -40,18 +55,26 @@ test.describe("Default Global Header", () => { .first(); await expect(helpDropdownButton).toBeVisible(); - await uiHelper.verifyLink({ label: "Notifications" }); + await uiHelper.verifyLink({ + label: t["plugin.global-header"][lang]["notifications.title"], + }); expect(await uiHelper.isBtnVisible("rhdh-qe-2")).toBeTruthy(); }); test("Verify that search modal and settings button in sidebar are not visible", async () => { - expect(await uiHelper.isBtnVisible("Search")).toBeFalsy(); - expect(await uiHelper.isBtnVisible("Settings")).toBeFalsy(); + expect( + await uiHelper.isBtnVisible(t["rhdh"][lang]["app.search.title"]), + ).toBeFalsy(); + expect( + await uiHelper.isBtnVisible(t["user-settings"][lang]["sidebarTitle"]), + ).toBeFalsy(); }); test("Verify that clicking on Self-service button opens the Templates page", async () => { - await uiHelper.clickLink({ ariaLabel: "Self-service" }); - await uiHelper.verifyHeading("Self-service"); + await uiHelper.clickLink({ + ariaLabel: t["rhdh"][lang]["menuItem.selfService"], + }); + await uiHelper.verifyHeading(t["rhdh"][lang]["menuItem.selfService"]); }); test("Verify that clicking on Support button in HelpDropdown opens a new tab", async ({ @@ -61,7 +84,9 @@ test.describe("Default Global Header", () => { const globalHeader = page.locator("nav[id='global-header']"); const helpDropdownButton = globalHeader - .locator("button[aria-label='Help']") + .locator( + `button[aria-label='${t["plugin.global-header"][lang]["help.tooltip"]}']`, + ) .or( globalHeader.locator("button").filter({ has: page.locator("svg[data-testid='HelpOutlineIcon']"), @@ -72,7 +97,10 @@ test.describe("Default Global Header", () => { await helpDropdownButton.click(); await page.waitForTimeout(500); - await uiHelper.verifyTextVisible("Support"); + await uiHelper.verifyTextVisible( + t["plugin.global-header"][lang]["help.supportTitle"], + true, + ); const [newTab] = await Promise.all([ context.waitForEvent("page"), @@ -89,11 +117,21 @@ test.describe("Default Global Header", () => { test("Verify Profile Dropdown behaves as expected", async ({ page }) => { await uiHelper.openProfileDropdown(); - await uiHelper.verifyLinkVisible("Settings"); - await uiHelper.verifyTextVisible("Sign out"); + await uiHelper.verifyLinkVisible( + t["user-settings"][lang]["settingsLayout.title"], + ); + await uiHelper.verifyTextVisible( + t["plugin.global-header"][lang]["profile.signOut"], + ); - await page.getByRole("menuitem", { name: "Settings" }).click(); - await uiHelper.verifyHeading("Settings"); + await page + .getByRole("menuitem", { + name: t["user-settings"][lang]["settingsLayout.title"], + }) + .click(); + await uiHelper.verifyHeading( + t["user-settings"][lang]["settingsLayout.title"], + ); await uiHelper.goToMyProfilePage(); await uiHelper.verifyTextInSelector("header > div > p", "user"); @@ -104,19 +142,25 @@ test.describe("Default Global Header", () => { ); await uiHelper.openProfileDropdown(); - await page.locator(`p`).getByText("Sign out").first().click(); - await uiHelper.verifyHeading("Select a sign-in method"); + await page + .locator(`p`) + .getByText(t["plugin.global-header"][lang]["profile.signOut"]) + .first() + .click(); + await uiHelper.verifyHeading(t["rhdh"][lang]["signIn.page.title"]); }); test("Verify Search bar behaves as expected", async ({ page }) => { - const searchBar = page.locator(`input[placeholder="Search..."]`); + const searchBar = page.locator( + `input[placeholder="${t["plugin.global-header"][lang]["search.placeholder"]}"]`, + ); await searchBar.click(); await searchBar.fill("test query term"); expect(await uiHelper.isBtnVisibleByTitle("Clear")).toBeTruthy(); const dropdownList = page.locator(`ul[role="listbox"]`); await expect(dropdownList).toBeVisible(); await searchBar.press("Enter"); - await uiHelper.verifyHeading("Search"); + await uiHelper.verifyHeading(t["rhdh"][lang]["app.search.title"]); const searchResultPageInput = page.locator( `input[id="search-bar-text-field"]`, ); @@ -130,10 +174,16 @@ test.describe("Default Global Header", () => { }) => { const notificationsBadge = page .locator("#global-header") - .getByRole("link", { name: "Notifications" }); + .getByRole("link", { + name: t["plugin.global-header"][lang]["notifications.title"], + }); - await uiHelper.clickLink({ ariaLabel: "Notifications" }); - await uiHelper.verifyHeading("Notifications"); + await uiHelper.clickLink({ + ariaLabel: t["plugin.global-header"][lang]["notifications.title"], + }); + await uiHelper.verifyHeading( + t["plugin.global-header"][lang]["notifications.title"], + ); await uiHelper.markAllNotificationsAsReadIfVisible(); const postResponse = await request.post(`${baseURL}/api/notifications`, { diff --git a/e2e-tests/playwright/e2e/extensions.spec.ts b/e2e-tests/playwright/e2e/extensions.spec.ts index a6056d04e9..f0a62974e7 100644 --- a/e2e-tests/playwright/e2e/extensions.spec.ts +++ b/e2e-tests/playwright/e2e/extensions.spec.ts @@ -3,6 +3,13 @@ import { Common } from "../utils/common"; import { UIhelper } from "../utils/ui-helper"; import { Extensions } from "../support/pages/extensions"; import { runAccessibilityTests } from "../utils/accessibility"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); test.describe("Admin > Extensions > Catalog", () => { let extensions: Extensions; @@ -10,20 +17,20 @@ test.describe("Admin > Extensions > Catalog", () => { const isMac = process.platform === "darwin"; const commonHeadings = [ - "Versions", - "Author", - "Tags", - "Category", - "Publisher", - "Support Provider", + t["plugin.marketplace"][lang]["metadata.versions"], + t["plugin.marketplace"][lang]["search.author"], + t["plugin.marketplace"][lang]["package.tags"], + t["plugin.marketplace"][lang]["metadata.category"], + t["plugin.marketplace"][lang]["metadata.publisher"], + t["plugin.marketplace"][lang]["metadata.supportProvider"], ]; const supportTypeOptions = [ - "Generally available", - "Certified", - "Custom plugin", - "Tech preview", - "Dev preview", - "Community plugin", + t["plugin.marketplace"][lang]["badges.generallyAvailable"], + t["plugin.marketplace"][lang]["badges.certified"], + t["plugin.marketplace"][lang]["badges.customPlugin"], + t["plugin.marketplace"][lang]["badges.techPreview"], + t["plugin.marketplace"][lang]["badges.devPreview"], + t["plugin.marketplace"][lang]["badges.communityPlugin"], ]; test.beforeAll(async () => { @@ -37,15 +44,23 @@ test.describe("Admin > Extensions > Catalog", () => { extensions = new Extensions(page); uiHelper = new UIhelper(page); await new Common(page).loginAsKeycloakUser(); - await uiHelper.openSidebarButton("Administration"); - await uiHelper.openSidebar("Extensions"); - await uiHelper.verifyHeading("Extensions"); + await uiHelper.openSidebarButton( + t["rhdh"][lang]["menuItem.administration"], + ); + await uiHelper.openSidebar(t["plugin.marketplace"][lang]["header.title"]); + await uiHelper.verifyHeading( + t["plugin.marketplace"][lang]["header.extensions"], + ); }); test("Verify search bar in extensions", async ({ page }) => { - await uiHelper.searchInputPlaceholder("Dynatrace"); + await uiHelper.searchInputAriaLabel("Dynatrace"); await uiHelper.verifyHeading("DynaTrace"); - await page.getByRole("button", { name: "Clear Search" }).click(); + await page + .getByRole("button", { + name: t["plugin.marketplace"][lang]["search.clear"], + }) + .click(); }); test("Verify category and author filters in extensions", async ({ @@ -55,16 +70,22 @@ test.describe("Admin > Extensions > Catalog", () => { await runAccessibilityTests(page, testInfo); - await uiHelper.clickTab("Catalog"); - await uiHelper.clickButton("CI/CD"); - await extensions.selectDropdown("Category"); + await uiHelper.clickTab(t["plugin.marketplace"][lang]["menuItem.catalog"]); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.category"], + ); + await extensions.toggleOption("CI/CD"); await page.getByRole("option", { name: "CI/CD" }).isChecked(); await page.keyboard.press(`Escape`); - await extensions.selectDropdown("Author"); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.author"], + ); await extensions.toggleOption("Red Hat"); await page.keyboard.press(`Escape`); await uiHelper.verifyHeading("Red Hat Argo CD"); - await uiHelper.verifyText("by Red Hat"); + await uiHelper.verifyText( + t["plugin.marketplace"][lang]["metadata.by"] + "Red Hat", + ); await page.getByRole("heading", { name: "Red Hat Argo CD" }).click(); await uiHelper.verifyTableHeadingAndRows([ "Package name", @@ -73,23 +94,41 @@ test.describe("Admin > Extensions > Catalog", () => { "Backstage compatibility version", "Status", ]); - await uiHelper.verifyHeading("Versions"); - await page.getByRole("button", { name: "close" }).click(); - await uiHelper.clickLink("Read more"); - await page.getByRole("button", { name: "close" }).click(); - await extensions.selectDropdown("Author"); + await uiHelper.verifyHeading( + t["plugin.marketplace"][lang]["metadata.versions"], + ); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await uiHelper.clickLink(t["plugin.marketplace"][lang]["common.readMore"]); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.author"], + ); await extensions.toggleOption("Red Hat"); await expect( page.getByRole("option", { name: "Red Hat" }).getByRole("checkbox"), ).not.toBeChecked(); await expect(page.getByRole("button", { name: "Red Hat" })).toBeHidden(); await page.keyboard.press(`Escape`); - await expect(page.getByLabel("Category").getByRole("combobox")).toBeEmpty(); + await expect( + page + .getByLabel(t["plugin.marketplace"][lang]["search.category"]) + .getByRole("combobox"), + ).toBeEmpty(); await page.keyboard.press(`Escape`); }); test("Verify support type filters in extensions", async ({ page }) => { - await extensions.selectDropdown("Support type"); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); await expect(page.getByRole("listbox")).toBeVisible(); // Verify all support type options are present @@ -98,27 +137,62 @@ test.describe("Admin > Extensions > Catalog", () => { } await page.keyboard.press("Escape"); - await expect(page.getByLabel("Category").getByRole("combobox")).toBeEmpty(); + await expect( + page + .getByLabel(t["plugin.marketplace"][lang]["search.category"]) + .getByRole("combobox"), + ).toBeEmpty(); }); test("Verify certified badge in extensions", async ({ page }) => { - await extensions.selectDropdown("Support type"); - await extensions.toggleOption("Certified"); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); + await extensions.toggleOption( + t["plugin.marketplace"][lang]["badges.certified"], + ); await page.keyboard.press(`Escape`); await uiHelper.verifyHeading("DynaTrace"); - await expect(page.getByLabel("Certified by Red Hat").first()).toBeVisible(); + await expect( + page + .getByLabel( + t["plugin.marketplace"][lang]["badges.certifiedBy"].replace( + "{{provider}}", + "Red Hat", + ), + ) + .first(), + ).toBeVisible(); await expect(extensions.badge.first()).toBeVisible(); await extensions.badge.first().hover(); - await uiHelper.verifyTextInTooltip("Certified by Red Hat"); + await uiHelper.verifyTextInTooltip( + t["plugin.marketplace"][lang]["badges.certifiedBy"].replace( + "{{provider}}", + "Red Hat", + ), + ); await uiHelper.verifyHeading("DynaTrace"); await page.getByRole("heading", { name: "DynaTrace" }).first().click(); - await page.getByRole("button", { name: "close" }).click(); - await uiHelper.clickLink("Read more"); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await uiHelper.clickLink(t["plugin.marketplace"][lang]["common.readMore"]); await expect( - page.getByLabel("Stable and secured by Red Hat").getByText("Certified"), + page + .getByLabel( + t["plugin.marketplace"][lang]["badges.stableAndSecured"].replace( + "{{provider}}", + "Red Hat", + ), + ) + .getByText(t["plugin.marketplace"][lang]["badges.certified"]), ).toBeVisible(); - await uiHelper.verifyText("About"); - await uiHelper.verifyHeading("Versions"); + await uiHelper.verifyText(t["plugin.marketplace"][lang]["metadata.about"]); + await uiHelper.verifyHeading( + t["plugin.marketplace"][lang]["metadata.versions"], + ); await uiHelper.verifyTableHeadingAndRows([ "Package name", "Version", @@ -126,30 +200,53 @@ test.describe("Admin > Extensions > Catalog", () => { "Backstage compatibility version", "Status", ]); - await page.getByRole("button", { name: "close" }).click(); - await extensions.selectDropdown("Support type"); - await extensions.toggleOption("Certified"); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); + await extensions.toggleOption( + t["plugin.marketplace"][lang]["badges.certified"], + ); }); test("Verify Generally available badge in extensions", async ({ page }) => { - await extensions.selectSupportTypeFilter("Generally available (GA)"); + await extensions.selectSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.generallyAvailable"], + ); await expect( page - .getByLabel("Generally available (GA) and supported by Red Hat") + .getByLabel( + t["plugin.marketplace"][lang]["badges.gaAndSupportedBy"].replace( + "{{provider}}", + "Red Hat", + ), + ) .first(), ).toBeVisible(); await expect(extensions.badge.first()).toBeVisible(); await extensions.badge.first().hover(); await uiHelper.verifyTextInTooltip( - "Generally available (GA) and supported by Red Hat", + t["plugin.marketplace"][lang]["badges.gaAndSupportedBy"].replace( + "{{provider}}", + "Red Hat", + ), ); - await uiHelper.clickLink("Read more"); + await uiHelper.clickLink(t["plugin.marketplace"][lang]["common.readMore"]); await expect( page - .getByLabel("Production-ready and supported by Red Hat") - .getByText("Generally available (GA)"), + .getByLabel( + t["plugin.marketplace"][lang]["badges.productionReadyBy"].replace( + "{{provider}}", + "Red Hat", + ), + ) + .getByText(t["plugin.marketplace"][lang]["badges.generallyAvailable"]), ).toBeVisible(); for (const heading of commonHeadings) { @@ -157,82 +254,137 @@ test.describe("Admin > Extensions > Catalog", () => { await uiHelper.verifyHeading(heading); } - await page.getByRole("button", { name: "close" }).click(); + await page + .getByRole("button", { + name: "close", + }) + .click(); - await extensions.resetSupportTypeFilter("Generally available (GA)"); + await extensions.resetSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.generallyAvailable"], + ); }); // Skipping below test due to the issue: https://issues.redhat.com/browse/RHDHBUGS-2104 test.skip("Verify custom plugin badge in extensions", async ({ page }) => { - await extensions.selectDropdown("Support type"); - await extensions.toggleOption("Custom plugin"); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); + await extensions.toggleOption( + t["plugin.marketplace"][lang]["badges.customPlugin"], + ); await page.keyboard.press(`Escape`); - await expect(page.getByLabel("Custom plugins").first()).toBeVisible(); + await expect( + page + .getByLabel( + t["plugin.marketplace"][lang]["supportTypes.customPlugins"].replace( + " ({{count}})", + "", + ), + ) + .first(), + ).toBeVisible(); await expect(extensions.badge.first()).toBeVisible(); await extensions.badge.first().hover(); - await uiHelper.verifyTextInTooltip("Custom plugins"); - await uiHelper.clickLink("Read more"); + await uiHelper.verifyTextInTooltip( + t["plugin.marketplace"][lang]["supportTypes.customPlugins"].replace( + " ({{count}})", + "", + ), + ); + await uiHelper.clickLink(t["plugin.marketplace"][lang]["common.readMore"]); await expect( - page.getByLabel("Plugins added by the administrator").getByText("Custom"), + page + .getByLabel(t["plugin.marketplace"][lang]["badges.addedByAdmin"]) + .getByText("Custom"), ).toBeVisible(); - await page.getByRole("button", { name: "close" }).click(); - await extensions.selectDropdown("Support type"); - await extensions.toggleOption("Custom plugin"); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await extensions.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); + await extensions.toggleOption( + t["plugin.marketplace"][lang]["badges.customPlugin"], + ); await page.keyboard.press(`Escape`); }); test("Verify tech preview badge in extensions", async () => { await extensions.verifySupportTypeBadge({ - supportType: "Tech preview (TP)", + supportType: t["plugin.marketplace"][lang]["badges.techPreview"], pluginName: "Bulk Import", - badgeLabel: "Plugin still in development", - badgeText: "Tech preview (TP)", + badgeLabel: t["plugin.marketplace"][lang]["badges.pluginInDevelopment"], + badgeText: t["plugin.marketplace"][lang]["badges.techPreview"], tooltipText: "", searchTerm: "Bulk Import", - headings: ["About", "Versions", ...commonHeadings], + headings: [ + t["plugin.marketplace"][lang]["metadata.about"], + t["plugin.marketplace"][lang]["metadata.versions"], + ...commonHeadings, + ], includeTable: true, includeAbout: false, }); }); test("Verify dev preview badge in extensions", async () => { - await extensions.selectSupportTypeFilter("Dev preview (DP)"); + await extensions.selectSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.devPreview"], + ); await uiHelper.verifyHeading("Developer Lightspeed"); await extensions.verifyPluginDetails({ pluginName: "Developer Lightspeed", - badgeLabel: "An early-stage, experimental", - badgeText: "Dev preview (DP)", + badgeLabel: + t["plugin.marketplace"][lang]["badges.earlyStageExperimental"], + badgeText: t["plugin.marketplace"][lang]["badges.devPreview"], headings: commonHeadings, includeTable: true, includeAbout: false, }); - await extensions.resetSupportTypeFilter("Dev preview (DP)"); + await extensions.resetSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.devPreview"], + ); }); test("Verify community plugin badge in extensions", async ({ page }) => { - await extensions.selectSupportTypeFilter("Community plugin"); + await extensions.selectSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.communityPlugin"], + ); await extensions.clickReadMoreByPluginTitle( "ServiceNow Integration for Red Hat Developer Hub", ); await expect( page - .getByLabel("Open-source plugins, no official support") - .getByText("Community plugin"), + .getByLabel(t["plugin.marketplace"][lang]["badges.openSourceNoSupport"]) + .getByText(t["plugin.marketplace"][lang]["badges.communityPlugin"]), ).toBeVisible(); - await uiHelper.verifyText("About"); + await uiHelper.verifyText(t["plugin.marketplace"][lang]["metadata.about"]); for (const heading of commonHeadings) { console.log(`Verifying heading: ${heading}`); await uiHelper.verifyHeading(heading); } - await expect(page.getByText("AuthorRed Hat")).toBeVisible(); + await expect( + page.getByText( + t["plugin.marketplace"][lang]["search.author"] + "Red Hat", + ), + ).toBeVisible(); - await page.getByRole("button", { name: "close" }).click(); - await extensions.resetSupportTypeFilter("Community plugin"); + await page + .getByRole("button", { + name: "close", + }) + .click(); + await extensions.resetSupportTypeFilter( + t["plugin.marketplace"][lang]["badges.communityPlugin"], + ); }); test.use({ @@ -246,28 +398,30 @@ test.describe("Admin > Extensions > Catalog", () => { .locator('div[class*="MuiAlertTitle-root"]') .first(); productionEnvAlert.getByText( - "Plugin installation is disabled in the production environment.", + t["plugin.marketplace"][lang]["alert.productionDisabled"], { exact: true }, ); await uiHelper.searchInputPlaceholder("Topology"); await page.getByRole("heading", { name: "Topology" }).first().click(); - await uiHelper.clickButton("View"); + await uiHelper.clickButton(t["plugin.marketplace"][lang]["actions.view"]); await uiHelper.verifyHeading("Application Topology for Kubernetes"); await uiHelper.verifyText( "- package: ./dynamic-plugins/dist/backstage-community-plugin-topology", ); await uiHelper.verifyText("disabled: false"); - await uiHelper.verifyText("Apply"); + await uiHelper.verifyText(t["plugin.marketplace"][lang]["common.apply"]); await uiHelper.verifyHeading("Default configuration"); - await uiHelper.clickButton("Apply"); + await uiHelper.clickButton(t["plugin.marketplace"][lang]["common.apply"]); await uiHelper.verifyText("pluginConfig:"); await uiHelper.verifyText("dynamicPlugins:"); - await uiHelper.clickTab("About the plugin"); + await uiHelper.clickTab( + t["plugin.marketplace"][lang]["install.aboutPlugin"], + ); await uiHelper.verifyHeading("Configuring The Plugin"); - await uiHelper.clickTab("Examples"); + await uiHelper.clickTab(t["plugin.marketplace"][lang]["install.examples"]); await uiHelper.clickByDataTestId("ContentCopyRoundedIcon"); await expect(page.getByRole("button", { name: "✔" })).toBeVisible(); - await uiHelper.clickButton("Reset"); + await uiHelper.clickButton(t["plugin.marketplace"][lang]["install.reset"]); await expect(page.getByText("pluginConfig:")).toBeHidden(); // eslint-disable-next-line playwright/no-conditional-in-test const modifier = isMac ? "Meta" : "Control"; @@ -281,8 +435,12 @@ test.describe("Admin > Extensions > Catalog", () => { ); expect(clipboardContent).not.toContain("pluginConfig:"); expect(clipboardContent).toContain("backstage-community.plugin-topology:"); - await uiHelper.clickButton("Back"); - await expect(page.getByRole("button", { name: "View" })).toBeVisible(); + await uiHelper.clickButton(t["plugin.marketplace"][lang]["install.back"]); + await expect( + page.getByRole("button", { + name: t["plugin.marketplace"][lang]["actions.view"], + }), + ).toBeVisible(); await uiHelper.verifyHeading("Application Topology for Kubernetes"); }); }); diff --git a/e2e-tests/playwright/e2e/localization/locale-test.spec.ts b/e2e-tests/playwright/e2e/localization/locale-test.spec.ts deleted file mode 100644 index b58db7a70f..0000000000 --- a/e2e-tests/playwright/e2e/localization/locale-test.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { - getTranslations, - getLocale, -} from "../../support/translations/settings"; -import { Common } from "../../utils/common"; -import { UIhelper } from "../../utils/ui-helper"; - -const t = getTranslations(); - -test.describe(`RHDH Localization - ${t.settings.rhdhLanguage}`, () => { - test.beforeEach(async ({ page }) => { - const common = new Common(page); - const uiHelper = new UIhelper(page); - await common.loginAsGuest(); - await uiHelper.goToPageUrl("/settings", "Settings"); - }); - - // Run tests only for the selected language - test(`Should display correct language section ARIA content in ${t.settings.rhdhLanguage}`, async ({ - page, - }) => { - await page.getByRole("button", { name: "Hide" }).click(); - await expect(page.getByRole("list").first()).toMatchAriaSnapshot(` - - listitem: - - text: Language - - paragraph: Change the language - `); - - await expect(page.getByTestId("select").locator("div")).toContainText( - t.settings.rhdhLanguage, - ); - await page - .getByTestId("select") - .getByRole("button", { name: t.settings.rhdhLanguage }) - .click(); - await expect(page.getByRole("listbox")).toMatchAriaSnapshot(` - - listbox: - - option "English" - - option "Français" - - option "Deutsch" - `); - const french = getLocale("fr"); - await page - .getByRole("option", { name: french.settings.rhdhLanguage }) - .click(); - await expect(page.getByTestId("select").locator("div")).toContainText( - french.settings.rhdhLanguage, - ); - }); -}); diff --git a/e2e-tests/playwright/e2e/localization/locale.ts b/e2e-tests/playwright/e2e/localization/locale.ts new file mode 100644 index 0000000000..3c712810dd --- /dev/null +++ b/e2e-tests/playwright/e2e/localization/locale.ts @@ -0,0 +1,32 @@ +import frRhdh from "../../../../translations/rhdh_v1.8_s3281-fr-C.json" with { type: "json" }; +import frCorePlugins from "../../../../translations/core-plugins_v1.8_s3281-fr-C.json" with { type: "json" }; +import frCommunityPlugins from "../../../../translations/community-plugins_v1.8_s3281-fr-C.json" with { type: "json" }; +import frRhdhPlugins from "../../../../translations/rhdh-plugins__v1.8_s3281-fr-C.json" with { type: "json" }; +import frMissingTranslations from "../../../../translations/test/missing-fr-translations.json" with { type: "json" }; + +import en from "../../../../translations/test/all-v1.8_s3281-en.json" with { type: "json" }; + +const fr = { + ...frRhdh, + ...frCorePlugins, + ...frCommunityPlugins, + ...frRhdhPlugins, + ...frMissingTranslations, +}; + +const locales = { en, fr }; +export type Locale = keyof typeof locales; + +export function getCurrentLanguage(): Locale { + const lang = process.env.LOCALE || "en"; + return lang as Locale; +} + +export function getLocale(lang: Locale = getCurrentLanguage()) { + return locales[lang] || locales.en; +} + +export function getTranslations() { + const lang = getCurrentLanguage(); + return getLocale(lang); +} diff --git a/e2e-tests/playwright/e2e/plugins/frontend/sidebar.spec.ts b/e2e-tests/playwright/e2e/plugins/frontend/sidebar.spec.ts index 1eed548a56..c5bda32391 100644 --- a/e2e-tests/playwright/e2e/plugins/frontend/sidebar.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/frontend/sidebar.spec.ts @@ -1,6 +1,10 @@ import { Page, test, expect } from "@playwright/test"; import { UIhelper } from "../../../utils/ui-helper"; import { Common, setupBrowser } from "../../../utils/common"; +import { getTranslations, getCurrentLanguage } from "../../localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); let page: Page; @@ -25,17 +29,23 @@ test.describe("Validate Sidebar Navigation Customization", () => { // Verify presence of 'References' menu and related items const referencesMenu = uiHelper.getSideBarMenuItem("References"); expect(referencesMenu).not.toBeNull(); - expect(referencesMenu.getByText("APIs")).not.toBeNull(); - expect(referencesMenu.getByText("Learning Paths")).not.toBeNull(); + expect( + referencesMenu.getByText(t["rhdh"][lang]["menuItem.apis"]), + ).not.toBeNull(); + expect( + referencesMenu.getByText(t["rhdh"][lang]["menuItem.learningPaths"]), + ).not.toBeNull(); // Verify 'Favorites' menu and 'Docs' submenu item const favoritesMenu = uiHelper.getSideBarMenuItem("Favorites"); - const docsMenuItem = favoritesMenu.getByText("Docs"); + const docsMenuItem = favoritesMenu.getByText( + t["rhdh"][lang]["menuItem.docs"], + ); expect(docsMenuItem).not.toBeNull(); // Open the 'Favorites' menu and navigate to 'Docs' await uiHelper.openSidebarButton("Favorites"); - await uiHelper.openSidebar("Docs"); + await uiHelper.openSidebar(t["rhdh"][lang]["menuItem.docs"]); // Verify if the Documentation page has loaded await uiHelper.verifyHeading("Documentation"); diff --git a/e2e-tests/playwright/e2e/settings.spec.ts b/e2e-tests/playwright/e2e/settings.spec.ts new file mode 100644 index 0000000000..85f7519adc --- /dev/null +++ b/e2e-tests/playwright/e2e/settings.spec.ts @@ -0,0 +1,97 @@ +import { test, expect } from "@playwright/test"; +import { Common } from "../utils/common"; +import { UIhelper } from "../utils/ui-helper"; +import { + getTranslations, + getCurrentLanguage, + getLocale, +} from "./localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); + +let uiHelper: UIhelper; + +test.describe(`Settings page`, () => { + test.beforeEach(async ({ page }) => { + const common = new Common(page); + uiHelper = new UIhelper(page); + await common.loginAsGuest(); + await uiHelper.goToPageUrl( + "/settings", + t["user-settings"][lang]["settingsLayout.title"], + ); + }); + + // Run tests only for the selected language + test(`Verify settings page`, async ({ page }) => { + await page + .getByRole("button", { + name: t["plugin.quickstart"][lang]["footer.hide"], + }) + .click(); + await expect(page.getByRole("list").first()).toMatchAriaSnapshot(` + - listitem: + - text: ${t["user-settings"][lang]["languageToggle.title"]} + - paragraph: ${t["user-settings"][lang]["languageToggle.description"]} + `); + + await expect(page.getByTestId("select").locator("div")).toContainText( + /English|Français|Deutsch/, + ); + await page + .getByTestId("select") + .getByRole("button", { name: /English|Français|Deutsch/ }) + .click(); + await expect(page.getByRole("listbox")).toMatchAriaSnapshot(` + - listbox: + - option "English" + - option "Français" + - option "Deutsch" + `); + await page.getByRole("option", { name: "Français" }).click(); + await expect(page.getByTestId("select").locator("div")).toContainText( + "Français", + ); + + const fr = getLocale("fr"); + const langfr = "fr"; + + await uiHelper.verifyText(fr["user-settings"][langfr]["profileCard.title"]); + await uiHelper.verifyText( + fr["user-settings"][langfr]["appearanceCard.title"], + ); + await uiHelper.verifyText(fr["user-settings"][langfr]["themeToggle.title"]); + await page.getByTestId("user-settings-menu").click(); + await expect(page.getByTestId("sign-out")).toContainText( + fr["user-settings"][langfr]["signOutMenu.title"], + ); + await page.keyboard.press(`Escape`); + + await uiHelper.verifyText( + fr["user-settings"][langfr]["identityCard.title"], + ); + await uiHelper.verifyText( + fr["user-settings"][langfr]["identityCard.userEntity"] + ": Guest User", + ); + await uiHelper.verifyText( + fr["user-settings"][langfr]["identityCard.ownershipEntities"] + + ": Guest User, team-a", + ); + + await uiHelper.verifyText(fr["user-settings"][langfr]["pinToggle.title"]); + await uiHelper.verifyText( + fr["user-settings"][langfr]["pinToggle.description"], + ); + await uiHelper.uncheckCheckbox( + fr["user-settings"][langfr]["pinToggle.ariaLabelTitle"], + ); + await expect( + page.getByText(fr["rhdh"][langfr]["menuItem.apis"]), + ).toBeHidden(); + await uiHelper.checkCheckbox( + fr["user-settings"][langfr]["pinToggle.ariaLabelTitle"], + ); + await uiHelper.verifyText(fr["rhdh"][langfr]["menuItem.home"]); + }); +}); diff --git a/e2e-tests/playwright/support/page-objects/page-obj.ts b/e2e-tests/playwright/support/page-objects/page-obj.ts index 7dafa6aa5c..3d6406a48a 100644 --- a/e2e-tests/playwright/support/page-objects/page-obj.ts +++ b/e2e-tests/playwright/support/page-objects/page-obj.ts @@ -1,11 +1,19 @@ +import { + getTranslations, + getCurrentLanguage, +} from "../../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); + export const HOME_PAGE_COMPONENTS = { MuiAccordion: 'div[class*="MuiAccordion-root-"]', MuiCard: 'div[class*="MuiCard-root-"]', }; export const SEARCH_OBJECTS_COMPONENTS = { - ariaLabelSearch: 'input[aria-label="Search"]', - placeholderSearch: 'input[placeholder="Search"]', + ariaLabelSearch: `input[aria-label="${t["search-react"][lang]["searchBar.title"]}"]`, + placeholderSearch: `input[placeholder="${t["search-react"][lang]["searchBar.title"]}"]`, }; export const CATALOG_IMPORT_COMPONENTS = { diff --git a/e2e-tests/playwright/support/pages/catalog-import.ts b/e2e-tests/playwright/support/pages/catalog-import.ts index 93c359ae10..6d1cfbc525 100644 --- a/e2e-tests/playwright/support/pages/catalog-import.ts +++ b/e2e-tests/playwright/support/pages/catalog-import.ts @@ -6,6 +6,13 @@ import { } from "../page-objects/page-obj"; import { APIHelper } from "../../utils/api-helper"; import { GITHUB_API_ENDPOINTS } from "../../utils/api-endpoints"; +import { + getTranslations, + getCurrentLanguage, +} from "../../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); export class CatalogImport { private page: Page; @@ -24,7 +31,11 @@ export class CatalogImport { */ private async analyzeAndWait(url: string): Promise { await this.page.fill(CATALOG_IMPORT_COMPONENTS.componentURL, url); - await expect(await this.uiHelper.clickButton("Analyze")).not.toBeVisible({ + await expect( + await this.uiHelper.clickButton( + t["catalog-import"][lang]["stepInitAnalyzeUrl.nextButtonText"], + ), + ).not.toBeVisible({ timeout: 25_000, }); } @@ -36,7 +47,9 @@ export class CatalogImport { * @returns boolean indicating if the component is already registered */ async isComponentAlreadyRegistered(): Promise { - return await this.uiHelper.isBtnVisible("Refresh"); + return await this.uiHelper.isBtnVisible( + t["catalog-import"][lang]["stepReviewLocation.refresh"], + ); } /** @@ -54,12 +67,24 @@ export class CatalogImport { const isComponentAlreadyRegistered = await this.isComponentAlreadyRegistered(); if (isComponentAlreadyRegistered) { - await this.uiHelper.clickButton("Refresh"); - expect(await this.uiHelper.isBtnVisible("Register another")).toBeTruthy(); + await this.uiHelper.clickButton( + t["catalog-import"][lang]["stepReviewLocation.refresh"], + ); + expect( + await this.uiHelper.isBtnVisible( + t["catalog-import"][lang]["stepFinishImportLocation.backButtonText"], + ), + ).toBeTruthy(); } else { - await this.uiHelper.clickButton("Import"); + await this.uiHelper.clickButton( + t["catalog-import"][lang]["stepFinishImportLocation.import"], + ); if (clickViewComponent) { - await this.uiHelper.clickButton("View Component"); + await this.uiHelper.clickButton( + t["catalog-import"][lang][ + "stepFinishImportLocation.locations.viewButtonText" + ], + ); } } return isComponentAlreadyRegistered; @@ -67,7 +92,9 @@ export class CatalogImport { async analyzeComponent(url: string) { await this.page.fill(CATALOG_IMPORT_COMPONENTS.componentURL, url); - await this.uiHelper.clickButton("Analyze"); + await this.uiHelper.clickButton( + t["catalog-import"][lang]["stepInitAnalyzeUrl.nextButtonText"], + ); } async inspectEntityAndVerifyYaml(text: string) { diff --git a/e2e-tests/playwright/support/pages/extensions.ts b/e2e-tests/playwright/support/pages/extensions.ts index 8e77393b42..193c0d78ec 100644 --- a/e2e-tests/playwright/support/pages/extensions.ts +++ b/e2e-tests/playwright/support/pages/extensions.ts @@ -1,5 +1,12 @@ import { Page, expect, Locator } from "@playwright/test"; import { UIhelper } from "../../utils/ui-helper"; +import { + getTranslations, + getCurrentLanguage, +} from "../../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); export class Extensions { private page: Page; @@ -7,12 +14,12 @@ export class Extensions { private uiHelper: UIhelper; private commonHeadings = [ - "Versions", - "Author", - "Tags", - "Category", - "Publisher", - "Support Provider", + t["plugin.marketplace"][lang]["metadata.versions"], + t["plugin.marketplace"][lang]["search.author"], + t["plugin.marketplace"][lang]["package.tags"], + t["plugin.marketplace"][lang]["metadata.category"], + t["plugin.marketplace"][lang]["metadata.publisher"], + t["plugin.marketplace"][lang]["metadata.supportProvider"], ]; private tableHeaders = [ "Package name", @@ -31,7 +38,11 @@ export class Extensions { async clickReadMoreByPluginTitle(pluginTitle: string) { const allCards = this.page.locator(".v5-MuiPaper-outlined"); const targetCard = allCards.filter({ hasText: pluginTitle }); - await targetCard.getByRole("link", { name: "Read more" }).click(); + await targetCard + .getByRole("link", { + name: t["plugin.marketplace"][lang]["common.readMore"], + }) + .click(); } async selectDropdown(name: string) { @@ -53,13 +64,17 @@ export class Extensions { } async selectSupportTypeFilter(supportType: string) { - await this.selectDropdown("Support type"); + await this.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); await this.toggleOption(supportType); await this.page.keyboard.press("Escape"); } async resetSupportTypeFilter(supportType: string) { - await this.selectDropdown("Support type"); + await this.selectDropdown( + t["plugin.marketplace"][lang]["search.supportType"], + ); await this.toggleOption(supportType); await this.page.keyboard.press("Escape"); } @@ -98,7 +113,9 @@ export class Extensions { ).toBeVisible(); if (includeAbout) { - await this.uiHelper.verifyText("About"); + await this.uiHelper.verifyText( + t["plugin.marketplace"][lang]["metadata.about"], + ); } await this.verifyMultipleHeadings(headings); @@ -107,7 +124,11 @@ export class Extensions { await this.uiHelper.verifyTableHeadingAndRows(this.tableHeaders); } - await this.page.getByRole("button", { name: "close" }).click(); + await this.page + .getByRole("button", { + name: "close", + }) + .click(); } async verifySupportTypeBadge({ diff --git a/e2e-tests/playwright/utils/common.ts b/e2e-tests/playwright/utils/common.ts index 8b20bafdc5..8d5735c994 100644 --- a/e2e-tests/playwright/utils/common.ts +++ b/e2e-tests/playwright/utils/common.ts @@ -5,6 +5,13 @@ import { SETTINGS_PAGE_COMPONENTS } from "../support/page-objects/page-obj"; import { WAIT_OBJECTS } from "../support/page-objects/global-obj"; import * as path from "path"; import * as fs from "fs"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; + +const t = getTranslations(); +const lang = getCurrentLanguage(); export class Common { page: Page; @@ -25,8 +32,10 @@ export class Common { await dialog.accept(); }); - await this.uiHelper.verifyHeading("Select a sign-in method"); - await this.uiHelper.clickButton("Enter"); + await this.uiHelper.verifyHeading(t["rhdh"][lang]["signIn.page.title"]); + await this.uiHelper.clickButton( + t["core-components"][lang]["signIn.guestProvider.enter"], + ); await this.uiHelper.waitForSideBarVisible(); } @@ -42,7 +51,7 @@ export class Common { async signOut() { await this.page.click(SETTINGS_PAGE_COMPONENTS.userSettingsMenu); await this.page.click(SETTINGS_PAGE_COMPONENTS.signOut); - await this.uiHelper.verifyHeading("Select a sign-in method"); + await this.uiHelper.verifyHeading(t["rhdh"][lang]["signIn.page.title"]); } private async logintoGithub(userid: string) { @@ -98,7 +107,7 @@ export class Common { ) { await this.page.goto("/"); await this.waitForLoad(240000); - await this.uiHelper.clickButton("Sign In"); + await this.uiHelper.clickButton(t["core-components"][lang]["signIn.title"]); await this.logintoKeycloak(userid, password); await this.uiHelper.waitForSideBarVisible(); } @@ -116,14 +125,18 @@ export class Common { console.log(`Reusing existing authentication state for user: ${userid}`); await this.page.goto("/"); await this.waitForLoad(12000); - await this.uiHelper.clickButton("Sign In"); + await this.uiHelper.clickButton( + t["core-components"][lang]["signIn.title"], + ); await this.checkAndReauthorizeGithubApp(); } else { // Perform login if no session file exists, then save the state await this.logintoGithub(userid); await this.page.goto("/"); await this.waitForLoad(240000); - await this.uiHelper.clickButton("Sign In"); + await this.uiHelper.clickButton( + t["core-components"][lang]["signIn.title"], + ); await this.checkAndReauthorizeGithubApp(); await this.uiHelper.waitForSideBarVisible(); await this.page.context().storageState({ path: sessionFileName }); @@ -189,9 +202,13 @@ export class Common { } async clickOnGHloginPopup() { - const isLoginRequiredVisible = await this.uiHelper.isTextVisible("Sign in"); + const isLoginRequiredVisible = await this.uiHelper.isTextVisible( + t["core-components"][lang]["signIn.title"], + ); if (isLoginRequiredVisible) { - await this.uiHelper.clickButton("Sign in"); + await this.uiHelper.clickButton( + t["core-components"][lang]["signIn.title"], + ); await this.uiHelper.clickButton("Log in"); await this.checkAndReauthorizeGithubApp(); await this.uiHelper.waitForLoginBtnDisappear(); @@ -228,8 +245,10 @@ export class Common { }); await this.page.goto("/"); - await this.page.waitForSelector('p:has-text("Sign in using OIDC")'); - await this.uiHelper.clickButton("Sign In"); + await this.page.waitForSelector( + `p:has-text("${t["rhdh"][lang]["signIn.providers.oidc.message"]}")`, + ); + await this.uiHelper.clickButton(t["core-components"][lang]["signIn.title"]); // Wait for the popup to appear await expect(async () => { @@ -314,11 +333,13 @@ export class Common { async githubLogin(username: string, password: string, twofactor: string) { await this.page.goto("/"); - await this.page.waitForSelector('p:has-text("Sign in using GitHub")'); + await this.page.waitForSelector( + `p:has-text("${t["rhdh"][lang]["signIn.providers.github.message"]}")`, + ); const [popup] = await Promise.all([ this.page.waitForEvent("popup"), - this.uiHelper.clickButton("Sign In"), + this.uiHelper.clickButton(t["core-components"][lang]["signIn.title"]), ]); return this.handleGitHubPopupLogin(popup, username, password, twofactor); @@ -333,7 +354,9 @@ export class Common { const [popup] = await Promise.all([ this.page.waitForEvent("popup"), - this.page.getByTitle("Sign in to GitHub").click(), + this.page + .getByTitle(t["rhdh"][lang]["signIn.providers.github.title"]) + .click(), this.uiHelper.clickButton("Log in"), ]); @@ -346,8 +369,10 @@ export class Common { }); await this.page.goto("/"); - await this.page.waitForSelector('p:has-text("Sign in using Microsoft")'); - await this.uiHelper.clickButton("Sign In"); + await this.page.waitForSelector( + `p:has-text("${t["rhdh"][lang]["signIn.providers.microsoft.message"]}")`, + ); + await this.uiHelper.clickButton(t["core-components"][lang]["signIn.title"]); // Wait for the popup to appear await expect(async () => { diff --git a/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts b/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts index 6a412e79ac..76d1e566df 100644 --- a/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts +++ b/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts @@ -1,7 +1,13 @@ import { Page, expect, TestInfo } from "@playwright/test"; import { UIhelper } from "../ui-helper"; import { UI_HELPER_ELEMENTS } from "../../support/page-objects/global-obj"; +import { + getTranslations, + getCurrentLanguage, +} from "../../e2e/localization/locale"; +const t = getTranslations(); +const lang = getCurrentLanguage(); export class ThemeVerifier { private readonly page: Page; private uiHelper: UIhelper; @@ -12,7 +18,10 @@ export class ThemeVerifier { } async setTheme(theme: "Light" | "Dark" | "Light Dynamic" | "Dark Dynamic") { - await this.uiHelper.goToPageUrl("/settings", "Settings"); + await this.uiHelper.goToPageUrl( + "/settings", + t["user-settings"][lang]["settingsLayout.title"], + ); await this.uiHelper.clickBtnByTitleIfNotPressed(`Select theme ${theme}`); const themeButton = this.page.getByRole("button", { name: theme, @@ -20,7 +29,10 @@ export class ThemeVerifier { }); // TODO: https://issues.redhat.com/browse/RHDHBUGS-2076 navigating back to settings page is needed until the issue is resolved - await this.uiHelper.goToPageUrl("/settings", "Settings"); + await this.uiHelper.goToPageUrl( + "/settings", + t["user-settings"][lang]["settingsLayout.title"], + ); await expect(themeButton).toHaveAttribute("aria-pressed", "true"); } diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 1a676ba6d0..1c6284b2c3 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -2,7 +2,13 @@ import { expect, Locator, Page } from "@playwright/test"; import { UI_HELPER_ELEMENTS } from "../support/page-objects/global-obj"; import { SidebarTabs } from "./navbar"; import { SEARCH_OBJECTS_COMPONENTS } from "../support/page-objects/page-obj"; +import { + getTranslations, + getCurrentLanguage, +} from "../e2e/localization/locale"; +const t = getTranslations(); +const lang = getCurrentLanguage(); export class UIhelper { private page: Page; @@ -51,6 +57,13 @@ export class UIhelper { await locator.check(); } + async uncheckCheckbox(text: string) { + const locator = this.page.getByRole("checkbox", { + name: text, + }); + await locator.uncheck(); + } + async clickButton( label: string | RegExp, options: { exact?: boolean; force?: boolean } = { @@ -234,7 +247,7 @@ export class UIhelper { async goToMyProfilePage() { await expect(this.page.locator("nav[id='global-header']")).toBeVisible(); await this.openProfileDropdown(); - await this.clickLink("My profile"); + await this.clickLink(t["plugin.global-header"][lang]["profile.myProfile"]); } async verifyLink( @@ -302,8 +315,12 @@ export class UIhelper { return await this.isElementVisible(locator, timeout); } - async verifyTextVisible(text: string, timeout = 10000): Promise { - const locator = this.page.getByText(text); + async verifyTextVisible( + text: string, + exact = false, + timeout = 10000, + ): Promise { + const locator = this.page.getByText(text, { exact }); await expect(locator).toBeVisible({ timeout }); } diff --git a/e2e-tests/tsconfig.json b/e2e-tests/tsconfig.json index 4722f2c6fe..a5507b1218 100644 --- a/e2e-tests/tsconfig.json +++ b/e2e-tests/tsconfig.json @@ -7,7 +7,8 @@ "skipLibCheck": true /* Skip type checking all .d.ts files. */, "module": "ESNext", "moduleResolution": "node", - "noEmit": true + "noEmit": true, + "resolveJsonModule": true }, "include": ["**/*.ts"] } diff --git a/translations/test/all-v1.8_s3281-en.json b/translations/test/all-v1.8_s3281-en.json new file mode 100644 index 0000000000..26bbb9dc2c --- /dev/null +++ b/translations/test/all-v1.8_s3281-en.json @@ -0,0 +1,1351 @@ +{ + "plugin.global-header": { + "en": { + "help.tooltip": "Help", + "help.noSupportLinks": "No support links", + "help.noSupportLinksSubtitle": "Your administrator needs to set up support links.", + "help.quickStart": "Quick start", + "help.supportTitle": "Support", + "profile.picture": "Profile picture", + "profile.settings": "Settings", + "profile.myProfile": "My profile", + "profile.signOut": "Sign out", + "search.placeholder": "Search...", + "search.noResults": "No results found", + "search.errorFetching": "Error fetching results", + "applicationLauncher.tooltip": "Application launcher", + "applicationLauncher.noLinksTitle": "No application links configured", + "applicationLauncher.noLinksSubtitle": "Configure application links in dynamic plugin config for quick access from here.", + "applicationLauncher.developerHub": "Developer Hub", + "applicationLauncher.rhdhLocal": "RHDH Local", + "applicationLauncher.sections.documentation": "Documentation", + "applicationLauncher.sections.developerTools": "Developer Tools", + "starred.title": "Your starred items", + "starred.removeTooltip": "Remove from list", + "starred.noItemsTitle": "No starred items yet", + "starred.noItemsSubtitle": "Click the star icon next to an entity's name to save it here for quick access.", + "notifications.title": "Notifications", + "notifications.unsupportedDismissOption": "Unsupported dismiss option \"{{option}}\", currently supported \"none\", \"session\" or \"localstorage\"!", + "create.title": "Self-service", + "create.registerComponent.title": "Register a component", + "create.registerComponent.subtitle": "Import it to the catalog page", + "create.templates.sectionTitle": "Use a template", + "create.templates.allTemplates": "All templates", + "create.templates.errorFetching": "Error fetching templates", + "create.templates.noTemplatesAvailable": "No templates available" + } + }, + "plugin.global-floating-action-button": { + "en": { + "fab.create.label": "Create", + "fab.create.tooltip": "Create entity", + "fab.docs.label": "Docs", + "fab.docs.tooltip": "Documentation", + "fab.apis.label": "APIs", + "fab.apis.tooltip": "API Documentation", + "fab.github.label": "GitHub", + "fab.github.tooltip": "GitHub Repository", + "fab.bulkImport.label": "Bulk Import", + "fab.bulkImport.tooltip": "Register multiple repositories in bulk", + "fab.quay.label": "Quay", + "fab.quay.tooltip": "Quay Container Registry", + "fab.menu.tooltip": "Menu" + } + }, + "plugin.homepage": { + "en": { + "header.welcome": "Welcome back!", + "header.welcomePersonalized": "Welcome back, {{name}}!", + "header.local": "Local", + "homePage.empty": "No home page cards (mount points) configured or found.", + "search.placeholder": "Search", + "quickAccess.title": "Quick Access", + "quickAccess.fetchError": "Could not fetch data.", + "quickAccess.error": "Unknown error", + "featuredDocs.learnMore": " Learn more", + "templates.title": "Explore Templates", + "templates.fetchError": "Could not fetch data.", + "templates.error": "Unknown error", + "templates.empty": "No templates added yet", + "templates.emptyDescription": "Once templates are added, this space will showcase relevant content tailored to your experience.", + "templates.register": "Register a template", + "templates.viewAll": "View all {{count}} templates", + "onboarding.greeting.goodMorning": "Good morning", + "onboarding.greeting.goodAfternoon": "Good afternoon", + "onboarding.greeting.goodEvening": "Good evening", + "onboarding.guest": "Guest", + "onboarding.getStarted.title": "Get started", + "onboarding.getStarted.description": "Learn about Red Hat Developer Hub.", + "onboarding.getStarted.buttonText": "Read documentation", + "onboarding.getStarted.ariaLabel": "Read documentation (opens in a new tab)", + "onboarding.explore.title": "Explore", + "onboarding.explore.description": "Explore components, APIs and templates.", + "onboarding.explore.buttonText": "Go to Catalog", + "onboarding.explore.ariaLabel": "Go to Catalog", + "onboarding.learn.title": "Learn", + "onboarding.learn.description": "Explore and develop new skills.", + "onboarding.learn.buttonText": "Go to Learning Paths", + "onboarding.learn.ariaLabel": "Go to Learning Paths", + "entities.title": "Explore Your Software Catalog", + "entities.fetchError": "Could not fetch data.", + "entities.error": "Unknown error", + "entities.description": "Browse the Systems, Components, Resources, and APIs that are available in your organization.", + "entities.close": "close", + "entities.empty": "No software catalog added yet", + "entities.emptyDescription": "Once software catalogs are added, this space will showcase relevant content tailored to your experience.", + "entities.register": "Register a component", + "entities.viewAll": "View all {{count}} catalog entities" + } + }, + "plugin.rbac": { + "en": { + "page.title": "RBAC", + "page.createRole": "Create role", + "page.editRole": "Edit role", + "table.searchPlaceholder": "Filter", + "table.labelRowsSelect": "Rows", + "table.title": "All roles", + "table.titleWithCount": "All roles ({{count}})", + "table.headers.name": "Name", + "table.headers.usersAndGroups": "Users and groups", + "table.headers.accessiblePlugins": "Accessible plugins", + "table.headers.actions": "Actions", + "table.emptyContent": "No records found", + "toolbar.createButton": "Create", + "toolbar.warning.title": "Unable to create role.", + "toolbar.warning.message": "To enable create/edit role button, make sure required users/groups are available in catalog as a role cannot be created without users/groups and also the role associated with your user should have the permission policies mentioned here.", + "toolbar.warning.linkText": "here", + "toolbar.warning.note": "Note", + "toolbar.warning.noteText": "Even after ingesting users/groups in catalog and applying above permissions if the create/edit button is still disabled then please contact your administrator as you might be conditionally restricted from accessing the create/edit button.", + "errors.notFound": "Not Found", + "errors.unauthorized": "Unauthorized to create role", + "errors.rbacDisabled": "Enable the RBAC backend plugin to use this feature.", + "errors.rbacDisabledInfo": "To enable RBAC, set `permission.enabled` to `true` in the app-config file.", + "errors.fetchRoles": "Something went wrong while fetching the roles", + "errors.fetchRole": "Something went wrong while fetching the role", + "errors.fetchPoliciesErr": "Error fetching the policies. {{error}}", + "errors.fetchPolicies": "Something went wrong while fetching the permission policies", + "errors.fetchPlugins": "Error fetching the plugins. {{error}}", + "errors.fetchConditionalPermissionPolicies": "Error fetching the conditional permission policies. {{error}}", + "errors.fetchConditions": "Something went wrong while fetching the role conditions", + "errors.fetchUsersAndGroups": "Something went wrong while fetching the users and groups", + "errors.createRole": "Unable to create role.", + "errors.editRole": "Unable to edit the role.", + "errors.deleteRole": "Unable to delete the role.", + "errors.roleCreatedSuccess": "Role was created successfully but unable to add permission policies to the role.", + "errors.roleCreatedConditionsSuccess": "Role created successfully but unable to add conditions to the role.", + "roleForm.titles.createRole": "Create Role", + "roleForm.titles.editRole": "Edit Role", + "roleForm.titles.nameAndDescription": "Enter name and description of role", + "roleForm.titles.usersAndGroups": "Add users and groups", + "roleForm.titles.permissionPolicies": "Add permission policies", + "roleForm.review.reviewAndCreate": "Review and create", + "roleForm.review.reviewAndSave": "Review and save", + "roleForm.review.nameDescriptionOwner": "Name, description, and owner of role", + "roleForm.steps.next": "Next", + "roleForm.steps.back": "Back", + "roleForm.steps.cancel": "Cancel", + "roleForm.steps.reset": "Reset", + "roleForm.steps.create": "Create", + "roleForm.steps.save": "Save", + "roleForm.fields.name.label": "Name", + "roleForm.fields.name.helperText": "Enter name of the role", + "roleForm.fields.description.label": "Description", + "roleForm.fields.description.helperText": "Enter a brief description about the role (The purpose of the role)", + "roleForm.fields.owner.label": "Owner", + "roleForm.fields.owner.helperText": "Optional: Enter a user or group who will have permission to edit this role and create additional roles. In the next step, specify which users they can assign to their roles and which plugins they can grant access to. If left blank, automatically assigns the author at creation.", + "deleteDialog.title": "Delete Role", + "deleteDialog.question": "Delete this role?", + "deleteDialog.confirmation": "Are you sure you want to delete the role **{{roleName}}**?\n\nDeleting this role is irreversible and will remove its functionality from the system. Proceed with caution.\n\nThe **{{members}}** associated with this role will lose access to all the **{{permissions}} permission policies** specified in this role.", + "deleteDialog.roleNameLabel": "Role name", + "deleteDialog.roleNameHelper": "Type the name of the role to confirm", + "deleteDialog.deleteButton": "Delete", + "deleteDialog.cancelButton": "Cancel", + "deleteDialog.successMessage": "Role {{roleName}} deleted successfully", + "snackbar.success": "Success", + "dialog.cancelRoleCreation": "Cancel role creation", + "dialog.exitRoleCreation": "Exit role creation?", + "dialog.exitRoleEditing": "Exit role editing?", + "dialog.exitWarning": "\n\nExiting this page will permanently discard the information you entered.\n\nAre you sure you want to exit?", + "dialog.discard": "Discard", + "dialog.cancel": "Cancel", + "conditionalAccess.condition": "Condition", + "conditionalAccess.allOf": "AllOf", + "conditionalAccess.anyOf": "AnyOf", + "conditionalAccess.not": "Not", + "conditionalAccess.addNestedCondition": "Add nested condition", + "conditionalAccess.addRule": "Add rule", + "conditionalAccess.nestedConditionTooltip": "Nested conditions are **1 layer rules within a main condition**. It lets you allow appropriate access by using detailed permissions based on various conditions. You can add multiple nested conditions.", + "conditionalAccess.nestedConditionExample": "For example, you can allow access to all entity types in the main condition and use a nested condition to limit the access to entities owned by the user.", + "permissionPolicies.helperText": "By default, users are not granted access to any plugins. To grant user access, select the plugins you want to enable. Then, select which actions you would like to give user permission to.", + "permissionPolicies.allPlugins": "All plugins ({{count}})", + "permissionPolicies.errorFetchingPolicies": "Error fetching the permission policies: {{error}}", + "permissionPolicies.resourceTypeTooltip": "resource type: {{resourceType}}", + "permissionPolicies.advancedPermissionsTooltip": "Use advanced customized permissions to allow access to specific parts of the selected resource type.", + "permissionPolicies.pluginsSelected": "{{count}} plugins", + "permissionPolicies.noPluginsSelected": "No plugins selected", + "permissionPolicies.search": "Search", + "permissionPolicies.noRecordsToDisplay": "No records to display.", + "permissionPolicies.selectedPluginsAppearHere": "Selected plugins appear here.", + "permissionPolicies.selectPlugins": "Select plugins", + "permissionPolicies.noPluginsFound": "No plugins found.", + "permissionPolicies.plugin": "Plugin", + "permissionPolicies.permission": "Permission", + "permissionPolicies.policies": "Policies", + "permissionPolicies.conditional": "Conditional", + "permissionPolicies.rules": "rules", + "permissionPolicies.rule": "rule", + "permissionPolicies.permissionPolicies": "Permission Policies", + "permissionPolicies.permissions": "permissions", + "common.noResults": "No results for this date range.", + "common.exportCSV": "Export CSV", + "common.csvFilename": "data-export.csv", + "common.noMembers": "No members", + "common.groups": "groups", + "common.group": "group", + "common.users": "users", + "common.user": "user", + "common.use": "Use", + "common.refresh": "Refresh", + "common.edit": "Edit", + "common.unauthorizedToEdit": "Unauthorized to edit", + "common.noRecordsFound": "No records found", + "common.selectUsersAndGroups": "Select users and groups", + "common.clearSearch": "clear search", + "common.closeDrawer": "Close the drawer", + "common.remove": "Remove", + "common.addRule": "Add rule", + "common.selectRule": "Select a rule", + "common.rule": "Rule", + "common.removeNestedCondition": "Remove nested condition", + "common.overview": "Overview", + "common.about": "About", + "common.description": "Description", + "common.modifiedBy": "Modified By", + "common.lastModified": "Last Modified", + "common.owner": "Owner", + "common.noUsersAndGroupsSelected": "No users and groups selected", + "common.selectedUsersAndGroupsAppearHere": "Selected users and groups appear here.", + "common.name": "Name", + "common.type": "Type", + "common.members": "Members", + "common.actions": "Actions", + "common.removeMember": "Remove member", + "common.delete": "Delete", + "common.deleteRole": "Delete Role", + "common.update": "Update", + "common.editRole": "Edit Role", + "common.checkingPermissions": "Checking permissions…", + "common.unauthorizedTo": "Unauthorized to {{action}}", + "common.performThisAction": "perform this action", + "common.unableToCreatePermissionPolicies": "Unable to create the permission policies.", + "common.unableToDeletePermissionPolicies": "Unable to delete the permission policies.", + "common.unableToRemoveConditions": "Unable to remove conditions from the role.", + "common.unableToUpdateConditions": "Unable to update conditions.", + "common.unableToAddConditions": "Unable to add conditions to the role.", + "common.roleActionSuccessfully": "Role {{roleName}} {{action}} successfully", + "common.unableToFetchRole": "Unable to fetch role: {{error}}", + "common.unableToFetchMembers": "Unable to fetch members: {{error}}", + "common.roleAction": "{{action}} role", + "common.membersCount": "{{count}} members", + "common.parentGroupCount": "{{count}} parent group", + "common.childGroupsCount": "{{count}} child groups", + "common.searchAndSelectUsersGroups": "Search and select users and groups to be added. Selected users and groups will appear in the table below.", + "common.noUsersAndGroupsFound": "No users and groups found.", + "common.errorFetchingUserGroups": "Error fetching user and groups: {{error}}", + "common.nameRequired": "Name is required", + "common.noMemberSelected": "No member selected", + "common.noPluginSelected": "No plugin selected", + "common.pluginRequired": "Plugin is required", + "common.permissionRequired": "Permission is required", + "common.editCell": "Edit...", + "common.selectCell": "Select...", + "common.expandRow": "expand row", + "common.configureAccessFor": "Configure access for the", + "common.defaultResourceTypeVisible": "By default, the selected resource type is visible to all added users. If you want to restrict or grant permission to specific plugin rules, select them and add the parameters." + } + }, + "plugin.adoption-insights": { + "en": { + "page.title": "Adoption Insights", + "header.title": "Adoption Insights", + "header.dateRange.today": "Today", + "header.dateRange.lastWeek": "Last week", + "header.dateRange.lastMonth": "Last month", + "header.dateRange.last28Days": "Last 28 days", + "header.dateRange.lastYear": "Last year", + "header.dateRange.dateRange": "Date range...", + "header.dateRange.cancel": "Cancel", + "header.dateRange.ok": "OK", + "header.dateRange.defaultLabel": "Last 28 days", + "activeUsers.title": "Active users", + "activeUsers.averagePrefix": "Average peak active user count was", + "activeUsers.averageText": "{{count}} per {{period}}", + "activeUsers.averageSuffix": " for this period.", + "activeUsers.hour": "hour", + "activeUsers.day": "day", + "activeUsers.week": "week", + "activeUsers.month": "month", + "activeUsers.legend.newUsers": "New users", + "activeUsers.legend.returningUsers": "Returning users", + "templates.title": "Top templates", + "templates.topNTitle": "Top {{count}} templates", + "templates.allTitle": "All templates", + "catalogEntities.title": "Top catalog entities", + "catalogEntities.topNTitle": "Top {{count}} catalog entities", + "catalogEntities.allTitle": "All catalog entities", + "plugins.title": "Top plugins", + "plugins.topNTitle": "Top {{count}} plugins", + "plugins.allTitle": "All plugins", + "techDocs.title": "Top TechDocs", + "techDocs.topNTitle": "Top {{count}} TechDocs", + "techDocs.allTitle": "All TechDocs", + "searches.title": "Top searches", + "searches.totalCount": "{{count}} searches", + "searches.averagePrefix": "Average search count was", + "searches.averageText": "{{count}} per {{period}}", + "searches.averageSuffix": " for this period.", + "searches.hour": "hour", + "searches.day": "day", + "searches.week": "week", + "searches.month": "month", + "users.title": "Total number of users", + "users.haveLoggedIn": "have logged in", + "users.loggedInUsers": "Logged-in users", + "users.licensed": "Licensed", + "users.licensedNotLoggedIn": "Licensed (not logged in)", + "users.ofTotal": "of {{total}}", + "users.tooltip": "Set the number of licensed users in the app-config.yaml", + "table.headers.name": "Name", + "table.headers.kind": "Kind", + "table.headers.lastUsed": "Last used", + "table.headers.views": "Views", + "table.headers.executions": "Executions", + "table.headers.trend": "Trend", + "table.headers.entity": "Entity", + "table.pagination.topN": "Top {{count}}", + "filter.all": "All", + "filter.selectKind": "Select kind", + "common.noResults": "No results for this date range.", + "common.readMore": "Read more", + "common.exportCSV": "Export CSV", + "common.downloading": "Downloading...", + "common.today": "Today", + "common.yesterday": "Yesterday", + "common.numberOfSearches": "Number of searches", + "common.filteredBy": "filtered by", + "common.invalidDateFormat": "Invalid date format", + "common.csvFilename": "active_users", + "permission.title": "Missing permissions", + "permission.description": "To view \"Adoption Insights\" plugin, contact your administrator to give the adoption-insights.events.read permissions." + } + }, + "plugin.marketplace": { + "en": { + "header.title": "Extensions", + "header.extensions": "Extensions", + "header.catalog": "Catalog", + "header.installedPackages": "Installed packages", + "header.installedPackagesWithCount": "Installed packages ({{count}})", + "header.pluginsPage": "Plugins", + "header.packagesPage": "Packages", + "header.collectionsPage": "Collections", + "button.install": "Install", + "button.uninstall": "Uninstall", + "button.enable": "Enable", + "button.disable": "Disable", + "button.update": "Update", + "button.save": "Save", + "button.close": "Close", + "button.viewAll": "View all plugins", + "button.viewDocumentation": "View documentation", + "button.viewInstalledPlugins": "View installed plugins ({{count}})", + "button.restart": "Restart required", + "status.notInstalled": "Not installed", + "status.installed": "Installed", + "status.disabled": "Disabled", + "status.partiallyInstalled": "Partially installed", + "status.updateAvailable": "Update available", + "role.backend": "Backend", + "role.backendModule": "Backend module", + "role.frontend": "Frontend", + "emptyState.noPluginsFound": "No plugins found", + "emptyState.mustEnableBackend": "Must enable the Extensions backend plugin", + "emptyState.noPluginsDescription": "There was an error with loading plugins. Check your configuration or review plugin documentation to resolve. You can also explore other available plugins.", + "emptyState.configureBackend": "Configure the '@red-hat-developer-hub/backstage-plugin-marketplace-backend' plugin.", + "alert.productionDisabled": "Plugin installation is disabled in the production environment.", + "alert.installationDisabled": "Plugin installation is disabled.", + "alert.extensionsExample": "Example how to enable extensions plugin installation", + "alert.singlePluginRestart": "The **{{pluginName}}** plugin requires a restart of the backend system to finish installing, updating, enabling or disabling.", + "alert.multiplePluginRestart": "You have **{{count}}** plugins that require a restart of your backend system to either finish installing, updating, enabling or disabling.", + "alert.singlePackageRestart": "The **{{packageName}}** package requires a restart of the backend system to finish installing, updating, enabling or disabling.", + "alert.multiplePackageRestart": "You have **{{count}}** packages that require a restart of your backend system to either finish installing, updating, enabling or disabling.", + "alert.restartRequired": "{{count}} plugins installed", + "alert.backendRestartRequired": "Backend restart required", + "alert.viewPlugins": "View plugins", + "alert.viewPackages": "View packages", + "search.placeholder": "Search", + "search.clear": "Clear Search", + "search.filter": "Filter", + "search.clearFilter": "Clear Filter", + "search.category": "Category", + "search.author": "Author", + "search.supportType": "Support type", + "search.noResults": "No plugins match your search criteria", + "search.filterBy": "Filter by", + "search.clearFilters": "Clear filters", + "search.noResultsFound": "No results found. Adjust your filters and try again.", + "common.links": "Links", + "common.by": " by ", + "common.comma": ", ", + "common.noDescriptionAvailable": "no description available", + "common.readMore": "Read more", + "common.close": "Close", + "common.apply": "Apply", + "common.couldNotApplyYaml": "Could not apply YAML: {{error}}", + "dialog.backendRestartRequired": "Backend restart required", + "dialog.packageRestartMessage": "To finish the package modifications, restart your backend system.", + "dialog.pluginRestartMessage": "To finish the plugin modifications, restart your backend system.", + "plugin.description": "Description", + "plugin.documentation": "Documentation", + "plugin.repository": "Repository", + "plugin.license": "License", + "plugin.version": "Version", + "plugin.author": "Author", + "plugin.authors": "Authors", + "plugin.tags": "Tags", + "plugin.dependencies": "Dependencies", + "plugin.configuration": "Configuration", + "plugin.installation": "Installation", + "package.name": "Package name:", + "package.version": "Version:", + "package.dynamicPluginPath": "Dynamic plugin path:", + "package.backstageRole": "Backstage role:", + "package.supportedVersions": "Supported versions:", + "package.author": "Author:", + "package.support": "Support:", + "package.lifecycle": "Lifecycle:", + "package.highlights": "Highlights", + "package.about": "About", + "package.notFound": "Package {{namespace}}/{{name}} not found!", + "table.packageName": "Package name", + "table.version": "Version", + "table.role": "Role", + "table.supportedVersion": "Supported version", + "table.status": "Status", + "table.name": "Name", + "table.action": "Action", + "table.description": "Description", + "table.versions": "Versions", + "table.plugins": "Plugins", + "table.packages": "Packages", + "table.pluginsCount": "Plugins ({{count}})", + "table.packagesCount": "Packages ({{count}})", + "table.pluginsTable": "Plugins table", + "installedPackages.table.title": "Installed packages ({{count}})", + "installedPackages.table.searchPlaceholder": "Search", + "installedPackages.table.columns.name": "Name", + "installedPackages.table.columns.packageName": "npm package name", + "installedPackages.table.columns.role": "Role", + "installedPackages.table.columns.version": "Version", + "installedPackages.table.columns.actions": "Actions", + "installedPackages.table.tooltips.enableActions": "To enable actions, add a catalog entity for this package", + "installedPackages.table.tooltips.noDownloadPermissions": "You don't have permission to download the configuration. Contact your administrator to request access or assistance.", + "installedPackages.table.tooltips.noEditPermissions": "You don't have permission to edit the configuration. Contact your administrator to request access or assistance.", + "installedPackages.table.tooltips.noTogglePermissions": "You don't have permission to enable or disable packages. Contact your administrator to request access or assistance.", + "installedPackages.table.tooltips.editPackage": "Edit package configuration", + "installedPackages.table.tooltips.downloadPackage": "Download package configuration", + "installedPackages.table.tooltips.enablePackage": "Enable package", + "installedPackages.table.tooltips.disablePackage": "Disable package", + "installedPackages.table.emptyMessages.noResults": "No results found. Try a different search term.", + "installedPackages.table.emptyMessages.noRecords": "No records to display", + "actions.install": "Install", + "actions.view": "View", + "actions.edit": "Edit", + "actions.enable": "Enable", + "actions.disable": "Disable", + "actions.actions": "Actions", + "actions.editConfiguration": "Edit", + "actions.pluginConfigurations": "Plugin configurations", + "actions.packageConfiguration": "Package configuration", + "actions.pluginCurrentlyEnabled": "Plugin currently enabled", + "actions.pluginCurrentlyDisabled": "Plugin currently disabled", + "actions.packageCurrentlyEnabled": "Package currently enabled", + "actions.packageCurrentlyDisabled": "Package currently disabled", + "actions.installTitle": "Install {{displayName}}", + "actions.editTitle": "Edit {{displayName}} configurations", + "metadata.by": " by ", + "metadata.pluginNotFound": "Plugin {{name}} not found!", + "metadata.highlights": "Highlights", + "metadata.about": "About", + "metadata.publisher": "Publisher", + "metadata.supportProvider": "Support Provider", + "metadata.entryName": "Entry name", + "metadata.bySomeone": "by someone", + "metadata.category": "Category", + "metadata.versions": "Versions", + "supportTypes.certifiedBy": "Certified by {{value}} ({{count}})", + "supportTypes.verifiedBy": "Verified by {{value}} ({{count}})", + "supportTypes.customPlugins": "Custom plugins ({{count}})", + "collection.kubernetes": "Kubernetes", + "collection.monitoring": "Monitoring", + "collection.security": "Security", + "collection.viewMore": "View more", + "collection.pluginCount": "{{count}} plugins", + "collection.featured.title": "Featured Plugins", + "collection.featured.description": "A curated collection of featured plugins recommended for most users", + "install.title": "Install Plugin", + "install.configurationRequired": "Configuration required", + "install.optional": "Optional", + "install.required": "Required", + "install.selectPackages": "Select packages to install", + "install.allPackages": "All packages", + "install.customConfiguration": "Custom configuration", + "install.installProgress": "Installing...", + "install.success": "Plugin installed successfully", + "install.error": "Failed to install plugin", + "install.installFrontend": "Install front-end plugin", + "install.installBackend": "Install back-end plugin", + "install.installTemplates": "Install software templates", + "install.installationInstructions": "Installation instructions", + "install.download": "Download", + "install.examples": "Examples", + "install.cancel": "Cancel", + "install.reset": "Reset", + "install.pluginTabs": "Plugin tabs", + "install.settingUpPlugin": "Setting up the plugin", + "install.aboutPlugin": "About the plugin", + "install.pluginUpdated": "Plugin updated", + "install.pluginInstalled": "Plugin installed", + "install.instructions": "Instructions", + "install.editInstructions": "Edit instructions", + "install.back": "Back", + "install.packageUpdated": "Package updated", + "install.packageEnabled": "Package enabled", + "install.packageDisabled": "Package disabled", + "install.errors.missingPluginsList": "Invalid editor content: missing 'plugins' list", + "install.errors.missingPackageItem": "Invalid editor content: missing package item", + "install.errors.missingPackageField": "Invalid editor content: 'package' field missing in item", + "install.errors.failedToSave": "Failed to save", + "loading": "Loading...", + "error": "An error occurred", + "retry": "Retry", + "errors.missingConfigFile": "Missing configuration file", + "errors.missingConfigMessage": "{{message}}. You need to add it to your app-config.yaml if you want to enable this tool. Edit the app-config.yaml file as shown in the example below:", + "errors.invalidConfigFile": "Invalid configuration file", + "errors.invalidConfigMessage": "Failed to load 'extensions.installation.saveToSingleFile.file'. {{message}}. Provide valid installation configuration if you want to enable this tool. Edit your dynamic-plugins.yaml file as shown in the example below:", + "errors.fileNotExists": "Configuration file is incorrect, misspelled or does not exist", + "errors.fileNotExistsMessage": "{{message}}. Please re-check the specified file name in your app-config.yaml if you want to enable this tool as highlighted in the example below:", + "errors.unknownError": "Error reading the configuration file. ", + "tooltips.productionDisabled": "Plugin installation is disabled in the production environment.", + "tooltips.extensionsDisabled": "Plugin installation is disabled. To enable it, update your extensions configuration in your app-config.yaml file.", + "tooltips.noPermissions": "You don't have permission to install plugins or view their configurations. Contact your administrator to request access or assistance.", + "aria.openPlugin": "Open plugin {{name}}", + "aria.closeDialog": "Close dialog", + "aria.expandSection": "Expand section", + "aria.collapseSection": "Collapse section", + "aria.sortBy": "Sort by {{field}}", + "aria.filterBy": "Filter by {{field}}", + "badges.certified": "Certified", + "badges.certifiedBy": "Certified by {{provider}}", + "badges.verified": "Verified", + "badges.verifiedBy": "Verified by {{provider}}", + "badges.customPlugin": "Custom plugin", + "badges.stableAndSecured": "Stable and secured by {{provider}}", + "badges.generallyAvailable": "Generally available (GA)", + "badges.gaAndSupportedBy": "Generally available (GA) and supported by {{provider}}", + "badges.gaAndSupported": "Generally available (GA) and supported", + "badges.productionReadyBy": "Production-ready and supported by {{provider}}", + "badges.productionReady": "Production-ready and supported", + "badges.communityPlugin": "Community plugin", + "badges.openSourceNoSupport": "Open-source plugins, no official support", + "badges.techPreview": "Tech preview (TP)", + "badges.pluginInDevelopment": "Plugin still in development", + "badges.devPreview": "Dev preview (DP)", + "badges.earlyStageExperimental": "An early-stage, experimental plugin", + "badges.addedByAdmin": "Plugins added by the administrator" + } + }, + "plugin.quickstart": { + "en": { + "header.title": "Let's get you started with Developer Hub", + "header.subtitle": "We'll guide you through a few quick steps", + "steps.setupAuthentication.title": "Set up authentication", + "steps.setupAuthentication.description": "Set up secure login credentials to protect your account from unauthorized access.", + "steps.setupAuthentication.ctaTitle": "Learn more", + "steps.configureRbac.title": "Configure RBAC", + "steps.configureRbac.description": "Assign roles and permissions to control who can view, create, or edit resources, ensuring secure and efficient collaboration.", + "steps.configureRbac.ctaTitle": "Manage access", + "steps.configureGit.title": "Configure Git", + "steps.configureGit.description": "Connect your Git providers, such as GitHub to manage code, automate workflows, and integrate with platform features.", + "steps.configureGit.ctaTitle": "Learn more", + "steps.managePlugins.title": "Manage plugins", + "steps.managePlugins.description": "Browse and install extensions to add features, connect with external tools, and customize your experience.", + "steps.managePlugins.ctaTitle": "Explore plugins", + "steps.importApplication.title": "Import application", + "steps.importApplication.description": "Import your existing code and services into the catalog to organize and access them through your developer portal.", + "steps.importApplication.ctaTitle": "Import", + "steps.learnAboutCatalog.title": "Learn about the Catalog", + "steps.learnAboutCatalog.description": "Discover all software components, services, and APIs, and view their owners and documentation.", + "steps.learnAboutCatalog.ctaTitle": "View Catalog", + "steps.exploreSelfServiceTemplates.title": "Explore Self-service templates", + "steps.exploreSelfServiceTemplates.description": "Use our self-service templates to quickly set up new projects, services, or documentation.", + "steps.exploreSelfServiceTemplates.ctaTitle": "Explore templates", + "steps.findAllLearningPaths.title": "Find all Learning Paths", + "steps.findAllLearningPaths.description": "Integrate tailored e-learning into your workflows with Learning Paths to accelerate onboarding, close skill gaps, and promote best practices.", + "steps.findAllLearningPaths.ctaTitle": "View Learning Paths", + "button.quickstart": "Quick start", + "button.openQuickstartGuide": "Open Quickstart Guide", + "button.closeDrawer": "Close Drawer", + "button.gotIt": "Got it!", + "footer.progress": "{{progress}}% progress", + "footer.notStarted": "Not started", + "footer.hide": "Hide", + "content.emptyState.title": "Quickstart content not available for your role.", + "item.expandAriaLabel": "Expand {{title}} details", + "item.collapseAriaLabel": "Collapse {{title}} details", + "item.expandButtonAriaLabel": "Expand item", + "item.collapseButtonAriaLabel": "Collapse item", + "dev.pageTitle": "Quickstart Plugin Test Page", + "dev.pageDescription": "This is a test page for the Quickstart plugin. Use the buttons below to interact with the quickstart drawer.", + "dev.drawerControls": "Drawer Controls", + "dev.currentState": "Current drawer state: {{state}}", + "dev.stateOpen": "Open", + "dev.stateClosed": "Closed", + "dev.instructions": "Instructions", + "dev.step1": "1. Click \"Open Quickstart Guide\" to open the drawer", + "dev.step2": "2. Navigate through the quickstart steps", + "dev.step3": "3. Test the progress tracking by completing steps", + "dev.step4": "4. The drawer can be closed using the close button or the drawer's own controls", + "dev.step5": "5. Progress is automatically saved to localStorage" + } + }, + "core-components": { + "en": { + "signIn.title": "Sign In", + "signIn.loginFailed": "Login failed", + "signIn.customProvider.title": "Custom User", + "signIn.customProvider.subtitle": "Enter your own User ID and credentials.\n This selection will not be stored.", + "signIn.customProvider.userId": "User ID", + "signIn.customProvider.tokenInvalid": "Token is not a valid OpenID Connect JWT Token", + "signIn.customProvider.continue": "Continue", + "signIn.customProvider.idToken": "ID Token (optional)", + "signIn.guestProvider.title": "Guest", + "signIn.guestProvider.subtitle": "Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.", + "signIn.guestProvider.enter": "Enter", + "skipToContent": "Skip to content", + "copyTextButton.tooltipText": "Text copied to clipboard", + "simpleStepper.reset": "Reset", + "simpleStepper.finish": "Finish", + "simpleStepper.next": "Next", + "simpleStepper.skip": "Skip", + "simpleStepper.back": "Back", + "errorPage.subtitle": "ERROR {{status}}: {{statusMessage}}", + "errorPage.title": "Looks like someone dropped the mic!", + "errorPage.goBack": "Go back", + "errorPage.showMoreDetails": "Show more details", + "errorPage.showLessDetails": "Show less details", + "emptyState.missingAnnotation.title": "Missing Annotation", + "emptyState.missingAnnotation.actionTitle": "Add the annotation to your component YAML as shown in the highlighted example below:", + "emptyState.missingAnnotation.readMore": "Read more", + "supportConfig.default.title": "Support Not Configured", + "supportConfig.default.linkTitle": "Add `app.support` config key", + "errorBoundary.title": "Please contact {{slackChannel}} for help.", + "oauthRequestDialog.title": "Login Required", + "oauthRequestDialog.authRedirectTitle": "This will trigger a http redirect to OAuth Login.", + "oauthRequestDialog.login": "Log in", + "oauthRequestDialog.rejectAll": "Reject All", + "oauthRequestDialog.message": "Sign-in to allow {{appTitle}} access to {{provider}} APIs and identities.", + "supportButton.title": "Support", + "supportButton.close": "Close", + "table.filter.title": "Filters", + "table.filter.clearAll": "Clear all", + "table.filter.placeholder": "All results", + "table.body.emptyDataSourceMessage": "No records to display", + "table.pagination.firstTooltip": "First Page", + "table.pagination.labelDisplayedRows": "{from}-{to} of {count}", + "table.pagination.labelRowsSelect": "rows", + "table.pagination.lastTooltip": "Last Page", + "table.pagination.nextTooltip": "Next Page", + "table.pagination.previousTooltip": "Previous Page", + "table.toolbar.search": "Filter", + "table.header.actions": "Actions", + "alertDisplay.message_one": "({{ count }} newer message)", + "alertDisplay.message_other": "({{ count }} newer messages)", + "autoLogout.stillTherePrompt.title": "Logging out due to inactivity", + "autoLogout.stillTherePrompt.buttonText": "Yes! Don't log me out", + "proxiedSignInPage.title": "You do not appear to be signed in. Please try reloading the browser page." + } + }, + "user-settings": { + "en": { + "languageToggle.title": "Language", + "languageToggle.description": "Change the language", + "languageToggle.select": "Select language {{language}}", + "themeToggle.title": "Theme", + "themeToggle.description": "Change the theme mode", + "themeToggle.select": "Select theme {{theme}}", + "themeToggle.selectAuto": "Select Auto Theme", + "themeToggle.names.light": "Light", + "themeToggle.names.dark": "Dark", + "themeToggle.names.auto": "Auto", + "signOutMenu.title": "Sign Out", + "signOutMenu.moreIconTitle": "more", + "pinToggle.title": "Pin Sidebar", + "pinToggle.description": "Prevent the sidebar from collapsing", + "pinToggle.switchTitles.unpin": "Unpin Sidebar", + "pinToggle.switchTitles.pin": "Pin Sidebar", + "pinToggle.ariaLabelTitle": "Pin Sidebar Switch", + "identityCard.title": "Backstage Identity", + "identityCard.noIdentityTitle": "No Backstage Identity", + "identityCard.userEntity": "User Entity", + "identityCard.ownershipEntities": "Ownership Entities", + "defaultProviderSettings.description": "Provides authentication towards {{provider}} APIs and identities", + "emptyProviders.title": "No Authentication Providers", + "emptyProviders.description": "You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.", + "emptyProviders.action.title": "Open app-config.yaml and make the changes as highlighted below:", + "emptyProviders.action.readMoreButtonTitle": "Read More", + "providerSettingsItem.title.signIn": "Sign in to {{title}}", + "providerSettingsItem.title.signOut": "Sign out from {{title}}", + "providerSettingsItem.buttonTitle.signIn": "Sign in", + "providerSettingsItem.buttonTitle.signOut": "Sign out", + "authProviders.title": "Available Providers", + "defaultSettingsPage.tabsTitle.general": "General", + "defaultSettingsPage.tabsTitle.authProviders": "Authentication Providers", + "defaultSettingsPage.tabsTitle.featureFlags": "Feature Flags", + "featureFlags.title": "Feature Flags", + "featureFlags.description": "Please refresh the page when toggling feature flags", + "featureFlags.emptyFlags.title": "No Feature Flags", + "featureFlags.emptyFlags.description": "Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc.", + "featureFlags.emptyFlags.action.title": "An example for how to add a feature flag is highlighted below:", + "featureFlags.emptyFlags.action.readMoreButtonTitle": "Read More", + "featureFlags.filterTitle": "Filter", + "featureFlags.clearFilter": "Clear filter", + "featureFlags.flagItem.title.disable": "Disable", + "featureFlags.flagItem.title.enable": "Enable", + "featureFlags.flagItem.subtitle.registeredInApplication": "Registered in the application", + "featureFlags.flagItem.subtitle.registeredInPlugin": "Registered in {{pluginId}} plugin", + "settingsLayout.title": "Settings", + "sidebarTitle": "Settings", + "profileCard.title": "Profile", + "appearanceCard.title": "Appearance" + } + }, + "catalog": { + "en": { + "indexPage.title": "{{orgName}} Catalog", + "indexPage.createButtonTitle": "Create", + "indexPage.supportButtonContent": "All your software catalog entities", + "aboutCard.title": "About", + "aboutCard.refreshButtonTitle": "Schedule entity refresh", + "aboutCard.editButtonTitle": "Edit Metadata", + "aboutCard.createSimilarButtonTitle": "Create something similar", + "aboutCard.refreshScheduledMessage": "Refresh scheduled", + "aboutCard.launchTemplate": "Launch Template", + "aboutCard.viewTechdocs": "View TechDocs", + "aboutCard.viewSource": "View Source", + "aboutCard.descriptionField.label": "Description", + "aboutCard.descriptionField.value": "No description", + "aboutCard.ownerField.label": "Owner", + "aboutCard.ownerField.value": "No Owner", + "aboutCard.domainField.label": "Domain", + "aboutCard.domainField.value": "No Domain", + "aboutCard.systemField.label": "System", + "aboutCard.systemField.value": "No System", + "aboutCard.parentComponentField.label": "Parent Component", + "aboutCard.parentComponentField.value": "No Parent Component", + "aboutCard.typeField.label": "Type", + "aboutCard.lifecycleField.label": "Lifecycle", + "aboutCard.tagsField.label": "Tags", + "aboutCard.tagsField.value": "No Tags", + "aboutCard.targetsField.label": "Targets", + "searchResultItem.lifecycle": "Lifecycle", + "searchResultItem.Owner": "Owner", + "catalogTable.warningPanelTitle": "Could not fetch catalog entities.", + "catalogTable.viewActionTitle": "View", + "catalogTable.editActionTitle": "Edit", + "catalogTable.starActionTitle": "Add to favorites", + "catalogTable.unStarActionTitle": "Remove from favorites", + "dependencyOfComponentsCard.title": "Dependency of components", + "dependencyOfComponentsCard.emptyMessage": "No component depends on this component", + "dependsOnComponentsCard.title": "Depends on components", + "dependsOnComponentsCard.emptyMessage": "No component is a dependency of this component", + "dependsOnResourcesCard.title": "Depends on resources", + "dependsOnResourcesCard.emptyMessage": "No resource is a dependency of this component", + "entityContextMenu.copiedMessage": "Copied!", + "entityContextMenu.moreButtonTitle": "More", + "entityContextMenu.inspectMenuTitle": "Inspect entity", + "entityContextMenu.copyURLMenuTitle": "Copy entity URL", + "entityContextMenu.unregisterMenuTitle": "Unregister entity", + "entityLabelsCard.title": "Labels", + "entityLabelsCard.emptyDescription": "No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:", + "entityLabelsCard.readMoreButtonTitle": "Read more", + "entityLabels.warningPanelTitle": "Entity not found", + "entityLabels.ownerLabel": "Owner", + "entityLabels.lifecycleLabel": "Lifecycle", + "entityLinksCard.title": "Links", + "entityLinksCard.emptyDescription": "No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:", + "entityLinksCard.readMoreButtonTitle": "Read more", + "entityNotFound.title": "Entity was not found", + "entityNotFound.description": "Want to help us build this? Check out our Getting Started documentation.", + "entityNotFound.docButtonTitle": "DOCS", + "deleteEntity.dialogTitle": "Are you sure you want to delete this entity?", + "deleteEntity.deleteButtonTitle": "Delete", + "deleteEntity.cancelButtonTitle": "Cancel", + "deleteEntity.description": "This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.", + "entityProcessingErrorsDescription": "The error below originates from", + "entityRelationWarningDescription": "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: ", + "hasComponentsCard.title": "Has components", + "hasComponentsCard.emptyMessage": "No component is part of this system", + "hasResourcesCard.title": "Has resources", + "hasResourcesCard.emptyMessage": "No resource is part of this system", + "hasSubcomponentsCard.title": "Has subcomponents", + "hasSubcomponentsCard.emptyMessage": "No subcomponent is part of this component", + "hasSubdomainsCard.title": "Has subdomains", + "hasSubdomainsCard.emptyMessage": "No subdomain is part of this domain", + "hasSystemsCard.title": "Has systems", + "hasSystemsCard.emptyMessage": "No system is part of this domain", + "relatedEntitiesCard.emptyHelpLinkTitle": "Learn how to change this", + "systemDiagramCard.title": "System Diagram", + "systemDiagramCard.description": "Use pinch & zoom to move around the diagram.", + "systemDiagramCard.edgeLabels.partOf": "part of", + "systemDiagramCard.edgeLabels.provides": "provides", + "systemDiagramCard.edgeLabels.dependsOn": "depends on" + } + }, + "scaffolder": { + "en": { + "aboutCard.launchTemplate": "Launch Template", + "actionsPage.title": "Installed actions", + "actionsPage.pageTitle": "Create a New Component", + "actionsPage.subtitle": "This is the collection of all installed actions", + "actionsPage.content.emptyState.title": "No information to display", + "actionsPage.content.emptyState.description": "There are no actions installed or there was an issue communicating with backend.", + "actionsPage.content.searchFieldPlaceholder": "Search for an action", + "actionsPage.action.input": "Input", + "actionsPage.action.output": "Output", + "actionsPage.action.examples": "Examples", + "fields.entityNamePicker.title": "Name", + "fields.entityNamePicker.description": "Unique name of the component", + "fields.entityPicker.title": "Entity", + "fields.entityPicker.description": "An entity from the catalog", + "fields.entityTagsPicker.title": "Tags", + "fields.entityTagsPicker.description": "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters", + "fields.multiEntityPicker.title": "Entity", + "fields.multiEntityPicker.description": "An entity from the catalog", + "fields.myGroupsPicker.title": "Entity", + "fields.myGroupsPicker.description": "An entity from the catalog", + "fields.ownedEntityPicker.title": "Entity", + "fields.ownedEntityPicker.description": "An entity from the catalog", + "fields.ownerPicker.title": "Owner", + "fields.ownerPicker.description": "The owner of the component", + "fields.azureRepoPicker.organization.title": "Organization", + "fields.azureRepoPicker.organization.description": "The Organization that this repo will belong to", + "fields.azureRepoPicker.project.title": "Project", + "fields.azureRepoPicker.project.description": "The Project that this repo will belong to", + "fields.bitbucketRepoPicker.workspaces.title": "Allowed Workspaces", + "fields.bitbucketRepoPicker.workspaces.inputTitle": "Workspaces", + "fields.bitbucketRepoPicker.workspaces.description": "The Workspace that this repo will belong to", + "fields.bitbucketRepoPicker.project.title": "Allowed Projects", + "fields.bitbucketRepoPicker.project.inputTitle": "Projects", + "fields.bitbucketRepoPicker.project.description": "The Project that this repo will belong to", + "fields.gerritRepoPicker.owner.title": "Owner", + "fields.gerritRepoPicker.owner.description": "The owner of the project (optional)", + "fields.gerritRepoPicker.parent.title": "Parent", + "fields.gerritRepoPicker.parent.description": "The project parent that the repo will belong to", + "fields.giteaRepoPicker.owner.title": "Owner Available", + "fields.giteaRepoPicker.owner.inputTitle": "Owner", + "fields.giteaRepoPicker.owner.description": "Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.", + "fields.githubRepoPicker.owner.title": "Owner Available", + "fields.githubRepoPicker.owner.inputTitle": "Owner", + "fields.githubRepoPicker.owner.description": "The organization, user or project that this repo will belong to", + "fields.gitlabRepoPicker.owner.title": "Owner Available", + "fields.gitlabRepoPicker.owner.inputTitle": "Owner", + "fields.gitlabRepoPicker.owner.description": "GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.", + "fields.repoUrlPicker.host.title": "Host", + "fields.repoUrlPicker.host.description": "The host where the repository will be created", + "fields.repoUrlPicker.repository.title": "Repositories Available", + "fields.repoUrlPicker.repository.inputTitle": "Repository", + "fields.repoUrlPicker.repository.description": "The name of the repository", + "listTaskPage.title": "List template tasks", + "listTaskPage.pageTitle": "Templates Tasks", + "listTaskPage.subtitle": "All tasks that have been started", + "listTaskPage.content.emptyState.title": "No information to display", + "listTaskPage.content.emptyState.description": "There are no tasks or there was an issue communicating with backend.", + "listTaskPage.content.tableTitle": "Tasks", + "listTaskPage.content.tableCell.taskID": "Task ID", + "listTaskPage.content.tableCell.template": "Template", + "listTaskPage.content.tableCell.created": "Created", + "listTaskPage.content.tableCell.owner": "Owner", + "listTaskPage.content.tableCell.status": "Status", + "ownerListPicker.title": "Task Owner", + "ownerListPicker.options.owned": "Owned", + "ownerListPicker.options.all": "All", + "ongoingTask.title": "Run of", + "ongoingTask.pageTitle.hasTemplateName": "Run of {{templateName}}", + "ongoingTask.pageTitle.noTemplateName": "Scaffolder Run", + "ongoingTask.subtitle": "Task {{taskId}}", + "ongoingTask.cancelButtonTitle": "Cancel", + "ongoingTask.retryButtonTitle": "Retry", + "ongoingTask.startOverButtonTitle": "Start Over", + "ongoingTask.hideLogsButtonTitle": "Hide Logs", + "ongoingTask.showLogsButtonTitle": "Show Logs", + "ongoingTask.contextMenu.hideLogs": "Hide Logs", + "ongoingTask.contextMenu.showLogs": "Show Logs", + "ongoingTask.contextMenu.hideButtonBar": "Hide Button Bar", + "ongoingTask.contextMenu.retry": "Retry", + "ongoingTask.contextMenu.showButtonBar": "Show Button Bar", + "ongoingTask.contextMenu.startOver": "Start Over", + "ongoingTask.contextMenu.cancel": "Cancel", + "templateEditorForm.stepper.emptyText": "There are no spec parameters in the template to preview.", + "renderSchema.tableCell.name": "Name", + "renderSchema.tableCell.title": "Title", + "renderSchema.tableCell.description": "Description", + "renderSchema.tableCell.type": "Type", + "renderSchema.undefined": "No schema defined", + "templatingExtensions.title": "Templating Extensions", + "templatingExtensions.pageTitle": "Templating Extensions", + "templatingExtensions.subtitle": "This is the collection of available templating extensions", + "templatingExtensions.content.emptyState.title": "No information to display", + "templatingExtensions.content.emptyState.description": "There are no templating extensions available or there was an issue communicating with the backend.", + "templatingExtensions.content.searchFieldPlaceholder": "Search for an extension", + "templatingExtensions.content.filters.title": "Filters", + "templatingExtensions.content.filters.notAvailable": "There are no template filters defined.", + "templatingExtensions.content.filters.metadataAbsent": "Filter metadata unavailable", + "templatingExtensions.content.filters.schema.input": "Input", + "templatingExtensions.content.filters.schema.arguments": "Arguments", + "templatingExtensions.content.filters.schema.output": "Output", + "templatingExtensions.content.filters.examples": "Examples", + "templatingExtensions.content.functions.title": "Functions", + "templatingExtensions.content.functions.notAvailable": "There are no global template functions defined.", + "templatingExtensions.content.functions.metadataAbsent": "Function metadata unavailable", + "templatingExtensions.content.functions.schema.arguments": "Arguments", + "templatingExtensions.content.functions.schema.output": "Output", + "templatingExtensions.content.functions.examples": "Examples", + "templatingExtensions.content.values.title": "Values", + "templatingExtensions.content.values.notAvailable": "There are no global template values defined.", + "templateTypePicker.title": "Categories", + "templateIntroPage.title": "Manage Templates", + "templateIntroPage.subtitle": "Edit, preview, and try out templates, forms, and custom fields", + "templateFormPage.title": "Template Editor", + "templateFormPage.subtitle": "Edit, preview, and try out templates forms", + "templateCustomFieldPage.title": "Custom Field Explorer", + "templateCustomFieldPage.subtitle": "Edit, preview, and try out custom fields", + "templateEditorPage.title": "Template Editor", + "templateEditorPage.subtitle": "Edit, preview, and try out templates and template forms", + "templateEditorPage.dryRunResults.title": "Dry-run results", + "templateEditorPage.dryRunResultsList.title": "Result {{resultId}}", + "templateEditorPage.dryRunResultsList.downloadButtonTitle": "Download as .zip", + "templateEditorPage.dryRunResultsList.deleteButtonTitle": "Delete result", + "templateEditorPage.dryRunResultsView.tab.files": "Files", + "templateEditorPage.dryRunResultsView.tab.log": "Log", + "templateEditorPage.dryRunResultsView.tab.output": "Output", + "templateEditorPage.taskStatusStepper.skippedStepTitle": "Skipped", + "templateEditorPage.customFieldExplorer.selectFieldLabel": "Choose Custom Field Extension", + "templateEditorPage.customFieldExplorer.fieldForm.title": "Field Options", + "templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle": "Apply", + "templateEditorPage.customFieldExplorer.fieldPreview.title": "Field Preview", + "templateEditorPage.customFieldExplorer.preview.title": "Template Spec", + "templateEditorPage.templateEditorBrowser.closeConfirmMessage": "Are you sure? Unsaved changes will be lost", + "templateEditorPage.templateEditorBrowser.saveIconTooltip": "Save all files", + "templateEditorPage.templateEditorBrowser.reloadIconTooltip": "Reload directory", + "templateEditorPage.templateEditorBrowser.closeIconTooltip": "Close directory", + "templateEditorPage.templateEditorIntro.title": "Get started by choosing one of the options below", + "templateEditorPage.templateEditorIntro.loadLocal.title": "Load Template Directory", + "templateEditorPage.templateEditorIntro.loadLocal.description": "Load a local template directory, allowing you to both edit and try executing your own template.", + "templateEditorPage.templateEditorIntro.loadLocal.unsupportedTooltip": "Only supported in some Chromium-based browsers with the page loaded over HTTPS", + "templateEditorPage.templateEditorIntro.createLocal.title": "Create New Template", + "templateEditorPage.templateEditorIntro.createLocal.description": "Create a local template directory, allowing you to both edit and try executing your own template.", + "templateEditorPage.templateEditorIntro.createLocal.unsupportedTooltip": "Only supported in some Chromium-based browsers with the page loaded over HTTPS", + "templateEditorPage.templateEditorIntro.formEditor.title": "Template Form Playground", + "templateEditorPage.templateEditorIntro.formEditor.description": "Preview and edit a template form, either using a sample template or by loading a template from the catalog.", + "templateEditorPage.templateEditorIntro.fieldExplorer.title": "Custom Field Explorer", + "templateEditorPage.templateEditorIntro.fieldExplorer.description": "View and play around with available installed custom field extensions.", + "templateEditorPage.templateEditorTextArea.saveIconTooltip": "Save file", + "templateEditorPage.templateEditorTextArea.refreshIconTooltip": "Reload file", + "templateEditorPage.templateEditorTextArea.emptyStateParagraph": "Please select an action on the file menu.", + "templateEditorPage.templateFormPreviewer.title": "Load Existing Template", + "templateListPage.title": "Create a new component", + "templateListPage.subtitle": "Create new software components using standard templates in your organization", + "templateListPage.pageTitle": "Create a new component", + "templateListPage.templateGroups.defaultTitle": "Templates", + "templateListPage.templateGroups.otherTitle": "Other Templates", + "templateListPage.contentHeader.registerExistingButtonTitle": "Register Existing Component", + "templateListPage.contentHeader.supportButtonTitle": "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).", + "templateListPage.additionalLinksForEntity.viewTechDocsTitle": "View TechDocs", + "templateWizardPage.title": "Create a new component", + "templateWizardPage.subtitle": "Create new software components using standard templates in your organization", + "templateWizardPage.pageTitle": "Create a new component", + "templateWizardPage.pageContextMenu.editConfigurationTitle": "Edit Configuration", + "templateEditorToolbar.customFieldExplorerTooltip": "Custom Fields Explorer", + "templateEditorToolbar.installedActionsDocumentationTooltip": "Installed Actions Documentation", + "templateEditorToolbar.templatingExtensionsDocumentationTooltip": "Templating Extensions Documentation", + "templateEditorToolbar.addToCatalogButton": "Publish", + "templateEditorToolbar.addToCatalogDialogTitle": "Publish changes", + "templateEditorToolbar.addToCatalogDialogContent.stepsIntroduction": "Follow the instructions below to create or update a template:", + "templateEditorToolbar.addToCatalogDialogContent.stepsListItems": "Save the template files in a local directory\nCreate a pull request to a new or existing git repository\nIf the template already exists, the changes will be reflected in the software catalog once the pull request gets merged\nBut if you are creating a new template, follow the documentation linked below to register the new template repository in software catalog", + "templateEditorToolbar.addToCatalogDialogActions.documentationButton": "Go to the documentation", + "templateEditorToolbar.addToCatalogDialogActions.documentationUrl": "https://backstage.io/docs/features/software-templates/adding-templates/", + "templateEditorToolbarFileMenu.button": "File", + "templateEditorToolbarFileMenu.options.openDirectory": "Open template directory", + "templateEditorToolbarFileMenu.options.createDirectory": "Create template directory", + "templateEditorToolbarFileMenu.options.closeEditor": "Close template editor", + "templateEditorToolbarTemplatesMenu.button": "Templates" + } + }, + "catalog-import": { + "en": { + "buttons.back": "Back", + "defaultImportPage.headerTitle": "Register an existing component", + "defaultImportPage.contentHeaderTitle": "Start tracking your component in {{appTitle}}", + "defaultImportPage.supportTitle": "Start tracking your component in {{appTitle}} by adding it to the software catalog.", + "importInfoCard.title": "Register an existing component", + "importInfoCard.deepLinkTitle": "Learn more about the Software Catalog", + "importInfoCard.linkDescription": "Enter the URL to your source code repository to add it to {{appTitle}}.", + "importInfoCard.fileLinkTitle": "Link to an existing entity file", + "importInfoCard.examplePrefix": "Example: ", + "importInfoCard.fileLinkDescription": "The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.", + "importInfoCard.githubIntegration.title": "Link to a repository", + "importInfoCard.githubIntegration.label": "GitHub only", + "importInfoCard.exampleDescription": "The wizard discovers all {{catalogFilename}} files in the repository, previews the entities, and adds them to the {{appTitle}} catalog.", + "importInfoCard.preparePullRequestDescription": "If no entities are found, the wizard will prepare a Pull Request that adds an example {{catalogFilename}} and prepares the {{appTitle}} catalog to load all entities as soon as the Pull Request is merged.", + "importStepper.singleLocation.title": "Select Locations", + "importStepper.singleLocation.description": "Discovered Locations: 1", + "importStepper.multipleLocations.title": "Select Locations", + "importStepper.multipleLocations.description": "Discovered Locations: {{length, number}}", + "importStepper.noLocation.title": "Create Pull Request", + "importStepper.noLocation.createPr.detailsTitle": "Pull Request Details", + "importStepper.noLocation.createPr.titleLabel": "Pull Request Title", + "importStepper.noLocation.createPr.titlePlaceholder": "Add Backstage catalog entity descriptor files", + "importStepper.noLocation.createPr.bodyLabel": "Pull Request Body", + "importStepper.noLocation.createPr.bodyPlaceholder": "A describing text with Markdown support", + "importStepper.noLocation.createPr.configurationTitle": "Entity Configuration", + "importStepper.noLocation.createPr.componentNameLabel": "Name of the created component", + "importStepper.noLocation.createPr.componentNamePlaceholder": "my-component", + "importStepper.noLocation.createPr.ownerLoadingText": "Loading groups…", + "importStepper.noLocation.createPr.ownerHelperText": "Select an owner from the list or enter a reference to a Group or a User", + "importStepper.noLocation.createPr.ownerErrorHelperText": "required value", + "importStepper.noLocation.createPr.ownerLabel": "Entity Owner", + "importStepper.noLocation.createPr.ownerPlaceholder": "my-group", + "importStepper.noLocation.createPr.codeownersHelperText": "WARNING: This may fail if no CODEOWNERS file is found at the target location.", + "importStepper.analyze.title": "Select URL", + "importStepper.prepare.title": "Import Actions", + "importStepper.prepare.description": "Optional", + "importStepper.review.title": "Review", + "importStepper.finish.title": "Finish", + "stepFinishImportLocation.backButtonText": "Register another", + "stepFinishImportLocation.repository.title": "The following Pull Request has been opened: ", + "stepFinishImportLocation.repository.description": "Your entities will be imported as soon as the Pull Request is merged.", + "stepFinishImportLocation.locations.new": "The following entities have been added to the catalog:", + "stepFinishImportLocation.locations.existing": "A refresh was triggered for the following locations:", + "stepFinishImportLocation.locations.viewButtonText": "View Component", + "stepFinishImportLocation.locations.backButtonText": "Register another", + "stepInitAnalyzeUrl.error.repository": "Couldn't generate entities for your repository", + "stepInitAnalyzeUrl.error.locations": "There are no entities at this location", + "stepInitAnalyzeUrl.error.default": "Received unknown analysis result of type {{type}}. Please contact the support team.", + "stepInitAnalyzeUrl.error.url": "Must start with http:// or https://.", + "stepInitAnalyzeUrl.urlHelperText": "Enter the full path to your entity file to start tracking your component", + "stepInitAnalyzeUrl.nextButtonText": "Analyze", + "stepPrepareCreatePullRequest.description": "You entered a link to a {{integrationType}} repository but a {{catalogFilename}} could not be found. Use this form to open a Pull Request that creates one.", + "stepPrepareCreatePullRequest.previewPr.title": "Preview Pull Request", + "stepPrepareCreatePullRequest.previewPr.subheader": "Create a new Pull Request", + "stepPrepareCreatePullRequest.previewCatalogInfo.title": "Preview Entities", + "stepPrepareCreatePullRequest.nextButtonText": "Create PR", + "stepPrepareSelectLocations.locations.description": "Select one or more locations that are present in your git repository:", + "stepPrepareSelectLocations.locations.selectAll": "Select All", + "stepPrepareSelectLocations.existingLocations.description": "These locations already exist in the catalog:", + "stepPrepareSelectLocations.nextButtonText": "Review", + "stepReviewLocation.prepareResult.title": "The following Pull Request has been opened: ", + "stepReviewLocation.prepareResult.description": "You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.", + "stepReviewLocation.catalog.exists": "The following locations already exist in the catalog:", + "stepReviewLocation.catalog.new": "The following entities will be added to the catalog:", + "stepReviewLocation.refresh": "Refresh", + "stepReviewLocation.import": "Import" + } + }, + "catalog-import-test": { + "en": { + "buttons.importExistingGitRepository": "Import an existing Git repository" + } + }, + "plugin.translations": { + "en": { + "page.title": "Translations", + "page.subtitle": "Manage and view loaded translations", + "table.title": "Loaded translations ({{count}})", + "table.headers.refId": "Ref ID", + "table.headers.key": "Key", + "table.options.pageSize": "Items per page", + "table.options.pageSizeOptions": "Show {{count}} items", + "export.title": "Translations", + "export.downloadButton": "Download default translations (English)", + "export.filename": "translations-{{timestamp}}.json", + "common.loading": "Loading...", + "common.error": "An error occurred", + "common.noData": "No data available", + "common.refresh": "Refresh", + "language.displayFormat": "{{displayName}} ({{code}})" + } + }, + "rhdh": { + "en": { + "menuItem.home": "Home", + "menuItem.myGroup": "My Group", + "menuItem.catalog": "Catalog", + "menuItem.apis": "APIs", + "menuItem.learningPaths": "Learning Paths", + "menuItem.selfService": "Self-service", + "menuItem.userSettings": "User Settings", + "menuItem.administration": "Administration", + "menuItem.extensions": "Extensions", + "menuItem.clusters": "Clusters", + "menuItem.rbac": "RBAC", + "menuItem.bulkImport": "Bulk import", + "menuItem.docs": "Docs", + "menuItem.lighthouse": "Lighthouse", + "menuItem.techRadar": "Tech Radar", + "menuItem.orchestrator": "Orchestrator", + "menuItem.adoptionInsights": "Adoption Insights", + "sidebar.menu": "Menu", + "sidebar.home": "Home", + "sidebar.homeLogo": "Home logo", + "signIn.page.title": "Select a sign-in method", + "signIn.providers.auth0.title": "Auth0", + "signIn.providers.auth0.message": "Sign in using Auth0", + "signIn.providers.atlassian.title": "Atlassian", + "signIn.providers.atlassian.message": "Sign in using Atlassian", + "signIn.providers.microsoft.title": "Microsoft", + "signIn.providers.microsoft.message": "Sign in using Microsoft", + "signIn.providers.bitbucket.title": "Bitbucket", + "signIn.providers.bitbucket.message": "Sign in using Bitbucket", + "signIn.providers.bitbucketServer.title": "Bitbucket Server", + "signIn.providers.bitbucketServer.message": "Sign in using Bitbucket Server", + "signIn.providers.github.title": "GitHub", + "signIn.providers.github.message": "Sign in using GitHub", + "signIn.providers.gitlab.title": "GitLab", + "signIn.providers.gitlab.message": "Sign in using GitLab", + "signIn.providers.google.title": "Google", + "signIn.providers.google.message": "Sign in using Google", + "signIn.providers.oidc.title": "OIDC", + "signIn.providers.oidc.message": "Sign in using OIDC", + "signIn.providers.okta.title": "Okta", + "signIn.providers.okta.message": "Sign in using Okta", + "signIn.providers.onelogin.title": "OneLogin", + "signIn.providers.onelogin.message": "Sign in using OneLogin", + "signIn.providers.saml.title": "SAML", + "signIn.providers.saml.message": "Sign in using SAML", + "app.scaffolder.title": "Self-service", + "app.search.title": "Search", + "app.search.resultType": "Result Type", + "app.search.softwareCatalog": "Software Catalog", + "app.search.filters.kind": "Kind", + "app.search.filters.lifecycle": "Lifecycle", + "app.search.filters.component": "Component", + "app.search.filters.template": "Template", + "app.search.filters.experimental": "experimental", + "app.search.filters.production": "production", + "app.learningPaths.title": "Learning Paths", + "app.learningPaths.error.title": "Could not fetch data.", + "app.learningPaths.error.unknownError": "Unknown error", + "app.entityPage.diagram.title": "System Diagram", + "app.userSettings.infoCard.title": "RHDH Metadata", + "app.userSettings.infoCard.metadataCopied": "Metadata copied to clipboard", + "app.userSettings.infoCard.copyMetadata": "Copy metadata to your clipboard", + "app.userSettings.infoCard.showLess": "Show less", + "app.userSettings.infoCard.showMore": "Show more", + "app.errors.contactSupport": "Contact support", + "app.errors.goBack": "Go back", + "app.errors.notFound.message": "We couldn't find that page", + "app.errors.notFound.additionalInfo": "The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.", + "app.table.createdAt": "Created At" + } + }, + "catalog-react": { + "en": { + "catalogFilter.title": "Filters", + "catalogFilter.buttonTitle": "Filters", + "entityKindPicker.title": "Kind", + "entityKindPicker.errorMessage": "Failed to load entity kinds", + "entityLifecyclePicker.title": "Lifecycle", + "entityNamespacePicker.title": "Namespace", + "entityOwnerPicker.title": "Owner", + "entityProcessingStatusPicker.title": "Processing Status", + "entityTagPicker.title": "Tags", + "entityPeekAheadPopover.title": "Drill into the entity to see all of the tags.", + "entityPeekAheadPopover.emailCardAction.title": "Email {{email}}", + "entityPeekAheadPopover.emailCardAction.subTitle": "mailto {{email}}", + "entityPeekAheadPopover.entityCardActionsTitle": "Show details", + "entitySearchBar.placeholder": "Search", + "entityTypePicker.title": "Type", + "entityTypePicker.errorMessage": "Failed to load entity types", + "entityTypePicker.optionAllTitle": "all", + "favoriteEntity.addToFavorites": "Add to favorites", + "favoriteEntity.removeFromFavorites": "Remove from favorites", + "inspectEntityDialog.title": "Entity Inspector", + "inspectEntityDialog.closeButtonTitle": "Close", + "inspectEntityDialog.ancestryPage.title": "Ancestry", + "inspectEntityDialog.colocatedPage.title": "Colocated", + "inspectEntityDialog.colocatedPage.description": "These are the entities that are colocated with this entity - as in, they originated from the same data source (e.g. came from the same YAML file), or from the same origin (e.g. the originally registered URL).", + "inspectEntityDialog.colocatedPage.alertNoLocation": "Entity had no location information.", + "inspectEntityDialog.colocatedPage.alertNoEntity": "There were no other entities on this location.", + "inspectEntityDialog.jsonPage.title": "Entity as JSON", + "inspectEntityDialog.jsonPage.description": "This is the raw entity data as received from the catalog, on JSON form.", + "inspectEntityDialog.overviewPage.title": "Overview", + "inspectEntityDialog.yamlPage.title": "Entity as YAML", + "inspectEntityDialog.yamlPage.description": "This is the raw entity data as received from the catalog, on YAML form.", + "unregisterEntityDialog.title": "Are you sure you want to unregister this entity?", + "unregisterEntityDialog.cancelButtonTitle": "Cancel", + "unregisterEntityDialog.deleteButtonTitle": "Delete Entity", + "unregisterEntityDialog.deleteEntitySuccessMessage": "Removed entity {{entityName}}", + "unregisterEntityDialog.bootstrapState.title": "You cannot unregister this entity, since it originates from a protected Backstage configuration (location \"{{location}}\"). If you believe this is in error, please contact the {{appTitle}} integrator.", + "unregisterEntityDialog.bootstrapState.advancedDescription": "You have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.", + "unregisterEntityDialog.bootstrapState.advancedOptions": "Advanced Options", + "unregisterEntityDialog.onlyDeleteStateTitle": "This entity does not seem to originate from a registered location. You therefore only have the option to delete it outright from the catalog.", + "unregisterEntityDialog.unregisterState.title": "This action will unregister the following entities:", + "unregisterEntityDialog.unregisterState.subTitle": "Located at the following location:", + "unregisterEntityDialog.unregisterState.description": "To undo, just re-register the entity in {{appTitle}}.", + "unregisterEntityDialog.unregisterState.unregisterButtonTitle": "Unregister Location", + "unregisterEntityDialog.unregisterState.advancedOptions": "Advanced Options", + "unregisterEntityDialog.unregisterState.advancedDescription": "You also have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.", + "unregisterEntityDialog.errorStateTitle": "Internal error: Unknown state", + "userListPicker.defaultOrgName": "Company", + "userListPicker.personalFilter.title": "Personal", + "userListPicker.personalFilter.ownedLabel": "Owned", + "userListPicker.personalFilter.starredLabel": "Starred", + "userListPicker.orgFilterAllLabel": "All" + } + }, + "search": { + "en": { + "searchModal.viewFullResults": "View Full Results", + "searchType.allResults": "All Results", + "searchType.tabs.allTitle": "All", + "searchType.accordion.allTitle": "All", + "searchType.accordion.collapse": "Collapse", + "searchType.accordion.numberOfResults": "{{number}} results", + "sidebarSearchModal.title": "Search" + } + }, + "search-react": { + "en": { + "searchBar.title": "Search", + "searchBar.placeholder": "Search in {{org}}", + "searchBar.clearButtonTitle": "Clear", + "searchFilter.allOptionTitle": "All", + "searchPagination.limitLabel": "Results per page:", + "searchPagination.limitText": "of {{num}}", + "noResultsDescription": "Sorry, no results were found", + "searchResultGroup.linkTitle": "See All", + "searchResultGroup.addFilterButtonTitle": "Add filter", + "searchResultPager.previous": "Previous", + "searchResultPager.next": "Next" + } + }, + "scaffolder-react": { + "en": { + "passwordWidget.content": "This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of `ui:widget: password`", + "scaffolderPageContextMenu.moreLabel": "more", + "scaffolderPageContextMenu.createLabel": "Create", + "scaffolderPageContextMenu.editorLabel": "Manage Templates", + "scaffolderPageContextMenu.actionsLabel": "Installed Actions", + "scaffolderPageContextMenu.tasksLabel": "Task List", + "scaffolderPageContextMenu.templatingExtensionsLabel": "Templating Extensions", + "stepper.backButtonText": "Back", + "stepper.createButtonText": "Create", + "stepper.reviewButtonText": "Review", + "stepper.stepIndexLabel": "Step {{index, number}}", + "stepper.nextButtonText": "Next", + "templateCategoryPicker.title": "Categories", + "templateCard.noDescription": "No description", + "templateCard.chooseButtonText": "Choose", + "cardHeader.detailBtnTitle": "Show template entity details", + "templateOutputs.title": "Text Output", + "workflow.noDescription": "No description" + } + }, + "api-docs": { + "en": { + "apiDefinitionDialog.closeButtonTitle": "Close", + "apiDefinitionDialog.tabsAriaLabel": "API definition options", + "apiDefinitionDialog.toggleButtonAriaLabel": "Toggle API Definition Dialog", + "defaultApiExplorerPage.title": "APIs", + "defaultApiExplorerPage.subtitle": "{{orgName}} API Explorer", + "defaultApiExplorerPage.pageTitleOverride": "APIs", + "defaultApiExplorerPage.createButtonTitle": "Register Existing API", + "defaultApiExplorerPage.supportButtonTitle": "All your APIs", + "consumedApisCard.title": "Consumed APIs", + "consumedApisCard.error.title": "Could not load APIs", + "consumedApisCard.emptyContent.title": "This {{entity}} does not consume any APIs.", + "hasApisCard.title": "APIs", + "hasApisCard.error.title": "Could not load APIs", + "hasApisCard.emptyContent.title": "This {{entity}} does not contain any APIs.", + "providedApisCard.title": "Provided APIs", + "providedApisCard.error.title": "Could not load APIs", + "providedApisCard.emptyContent.title": "This {{entity}} does not provide any APIs.", + "apiEntityColumns.typeTitle": "Type", + "apiEntityColumns.apiDefinitionTitle": "API Definition", + "consumingComponentsCard.title": "Consumers", + "consumingComponentsCard.error.title": "Could not load components", + "consumingComponentsCard.emptyContent.title": "No component consumes this API.", + "providingComponentsCard.title": "Providers", + "providingComponentsCard.error.title": "Could not load components", + "providingComponentsCard.emptyContent.title": "No component provides this API.", + "apisCardHelpLinkTitle": "Learn how to change this" + } + }, + "catalog-graph": { + "en": { + "catalogGraphCard.title": "Relations", + "catalogGraphCard.deepLinkTitle": "View graph", + "catalogGraphPage.title": "Catalog Graph", + "catalogGraphPage.filterToggleButtonTitle": "Filters", + "catalogGraphPage.supportButtonDescription": "Start tracking your component in by adding it to the software catalog.", + "catalogGraphPage.simplifiedSwitchLabel": "Simplified", + "catalogGraphPage.mergeRelationsSwitchLabel": "Merge relations", + "catalogGraphPage.zoomOutDescription": "Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.", + "catalogGraphPage.curveFilter.title": "Curve", + "catalogGraphPage.curveFilter.curveMonotoneX": "Monotone X", + "catalogGraphPage.curveFilter.curveStepBefore": "Step Before", + "catalogGraphPage.directionFilter.title": "Direction", + "catalogGraphPage.directionFilter.leftToRight": "Left to right", + "catalogGraphPage.directionFilter.rightToLeft": "Right to left", + "catalogGraphPage.directionFilter.topToBottom": "Top to bottom", + "catalogGraphPage.directionFilter.bottomToTop": "Bottom to top", + "catalogGraphPage.maxDepthFilter.title": "Max depth", + "catalogGraphPage.maxDepthFilter.inputPlaceholder": "∞ Infinite", + "catalogGraphPage.maxDepthFilter.clearButtonAriaLabel": "clear max depth", + "catalogGraphPage.selectedKindsFilter.title": "Kinds", + "catalogGraphPage.selectedRelationsFilter.title": "Relations" + } + }, + "org": { + "en": { + "groupProfileCard.groupNotFound": "Group not found", + "groupProfileCard.editIconButtonTitle": "Edit Metadata", + "groupProfileCard.refreshIconButtonTitle": "Schedule entity refresh", + "groupProfileCard.refreshIconButtonAriaLabel": "Refresh", + "groupProfileCard.listItemTitle.entityRef": "Entity Ref", + "groupProfileCard.listItemTitle.email": "Email", + "groupProfileCard.listItemTitle.parentGroup": "Parent Group", + "groupProfileCard.listItemTitle.childGroups": "Child Groups", + "membersListCard.title": "Members", + "membersListCard.subtitle": "of {{groupName}}", + "membersListCard.paginationLabel": ", page {{page}} of {{nbPages}}", + "membersListCard.noMembersDescription": "This group has no members.", + "membersListCard.aggregateMembersToggle.directMembers": "Direct Members", + "membersListCard.aggregateMembersToggle.aggregatedMembers": "Aggregated Members", + "membersListCard.aggregateMembersToggle.ariaLabel": "Users Type Switch", + "ownershipCard.title": "Ownership", + "ownershipCard.aggregateRelationsToggle.directRelations": "Direct Relations", + "ownershipCard.aggregateRelationsToggle.aggregatedRelations": "Aggregated Relations", + "ownershipCard.aggregateRelationsToggle.ariaLabel": "Ownership Type Switch", + "userProfileCard.userNotFound": "User not found", + "userProfileCard.editIconButtonTitle": "Edit Metadata", + "userProfileCard.listItemTitle.email": "Email", + "userProfileCard.listItemTitle.memberOf": "Member of", + "userProfileCard.moreGroupButtonTitle": "...More ({{number}})", + "userProfileCard.allGroupDialog.title": "All {{name}}'s groups:", + "userProfileCard.allGroupDialog.closeButtonTitle": "Close" + } + } +} \ No newline at end of file diff --git a/translations/test/missing-fr-translations.json b/translations/test/missing-fr-translations.json new file mode 100644 index 0000000000..4fe616a413 --- /dev/null +++ b/translations/test/missing-fr-translations.json @@ -0,0 +1,59 @@ +{ + "catalog-import-test": { + "fr": { + "buttons.importExistingGitRepository": "Importer un dépôt Git existant" + } + }, + "plugin.global-header": { + "fr": { + "search.placeholder": "Recherche...", + "help.tooltip": "Aide", + "help.supportTitle": "Support", + "notifications.title": "Notifications", + "profile.signOut": "Se déconnecter", + "profile.myProfile": "Mon profil" + } + }, + "plugin.marketplace": { + "fr": { + "header.title": "Extensions", + "header.extensions": "Extensions", + "header.catalog": "Catalogue", + "search.clear": "Effacer la recherche", + "search.category": "Catégorie", + "search.author": "Auteur", + "search.supportType": "Type de support", + "metadata.by": " par ", + "metadata.about": "À propos", + "metadata.versions": "Versions", + "metadata.category": "Catégorie", + "metadata.publisher": "Éditeur", + "metadata.supportProvider": "Fournisseur de support", + "package.tags": "Mots-clés", + "common.readMore": "En savoir plus", + "common.apply": "Appliquer", + "actions.view": "Voir", + "install.aboutPlugin": "À propos du plugin", + "install.examples": "Exemples", + "install.reset": "Réinitialiser", + "install.back": "Retour", + "alert.productionDisabled": "L'installation de plugins est désactivée dans l'environnement de production.", + "badges.generallyAvailable": "Généralement disponible (GA)", + "badges.certified": "Certifié", + "badges.customPlugin": "Plugin personnalisé", + "badges.techPreview": "Aperçu technique (TP)", + "badges.devPreview": "Aperçu du développement (DP)", + "badges.communityPlugin": "Plugin communautaire", + "badges.certifiedBy": "Certifié par {{provider}}", + "badges.stableAndSecured": "Stable et sécurisé par {{provider}}", + "badges.gaAndSupportedBy": "Généralement disponible (GA) et pris en charge par {{provider}}", + "badges.productionReadyBy": "Prêt pour la production et pris en charge par {{provider}}", + "badges.pluginInDevelopment": "Plugin toujours en développement", + "badges.earlyStageExperimental": "Un plugin expérimental en phase de démarrage", + "badges.openSourceNoSupport": "Plugins open source, pas de support officiel", + "badges.addedByAdmin": "Plugins ajoutés par l'administrateur", + "supportTypes.customPlugins": "Plugins personnalisés ({{count}})", + "menuItem.bulkImport": "Importation en masse" + } + } +} \ No newline at end of file