From 4446978fa3b496866a50d2150e61a761be6ab286 Mon Sep 17 00:00:00 2001 From: Kyrylo Hudym-Levkovych Date: Fri, 27 Oct 2023 21:12:23 +0300 Subject: [PATCH 1/2] feat: add i18n support for sr-only message --- src/ProductTour/Checkpoint.jsx | 10 +- src/ProductTour/Checkpoint.test.jsx | 17 +-- src/ProductTour/ProductTour.test.jsx | 191 +++++---------------------- 3 files changed, 48 insertions(+), 170 deletions(-) diff --git a/src/ProductTour/Checkpoint.jsx b/src/ProductTour/Checkpoint.jsx index 99d659ef8b..00bdc277aa 100644 --- a/src/ProductTour/Checkpoint.jsx +++ b/src/ProductTour/Checkpoint.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useMediaQuery } from 'react-responsive'; import PropTypes from 'prop-types'; import { createPopper } from '@popperjs/core'; +import { FormattedMessage } from 'react-intl'; import breakpoints from '../utils/breakpoints'; @@ -95,8 +96,13 @@ const Checkpoint = React.forwardRef(({ role="dialog" style={{ visibility: checkpointVisible ? 'visible' : 'hidden', pointerEvents: checkpointVisible ? 'auto' : 'none' }} > - {/* This text is not translated due to Paragon's lack of i18n support */} - Top of step {index + 1} + + + {(title || !isOnlyCheckpoint) && (
{title} diff --git a/src/ProductTour/Checkpoint.test.jsx b/src/ProductTour/Checkpoint.test.jsx index abb440ca68..2ea1aabb52 100644 --- a/src/ProductTour/Checkpoint.test.jsx +++ b/src/ProductTour/Checkpoint.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { IntlProvider } from 'react-intl'; import * as popper from '@popperjs/core'; @@ -24,7 +25,7 @@ describe('Checkpoint', () => { describe('second Checkpoint in Tour', () => { beforeEach(() => { render( - <> +
...
{ title="Checkpoint title" totalCheckpoints={5} /> - , +
, ); }); @@ -75,7 +76,7 @@ describe('Checkpoint', () => { describe('last Checkpoint in Tour', () => { beforeEach(() => { render( - <> +
{ title="Checkpoint title" totalCheckpoints={5} /> - , + , ); }); @@ -108,7 +109,7 @@ describe('Checkpoint', () => { describe('only one Checkpoint in Tour', () => { beforeEach(() => { render( - <> +
{ title="Checkpoint title" totalCheckpoints={1} /> - , + , ); }); @@ -140,7 +141,7 @@ describe('Checkpoint', () => { describe('only one Checkpoint in Tour and showDismissButton set to true', () => { it('it renders dismiss button and end button', () => { render( - <> +
{ totalCheckpoints={1} showDismissButton /> - , + , ); expect(screen.getByText('Dismiss', { selector: 'button' })).toBeInTheDocument(); expect(screen.getByText('End', { selector: 'button' })).toBeInTheDocument(); diff --git a/src/ProductTour/ProductTour.test.jsx b/src/ProductTour/ProductTour.test.jsx index 4b31569101..d6b514848f 100644 --- a/src/ProductTour/ProductTour.test.jsx +++ b/src/ProductTour/ProductTour.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { IntlProvider } from 'react-intl'; import * as popper from '@popperjs/core'; @@ -85,17 +86,20 @@ describe('', () => { popperMock.mockReset(); }); + // eslint-disable-next-line react/prop-types + function ProductTourWrapper({ tours }) { + return ( + + + {targets} + + ); + } + describe('one enabled tour', () => { describe('with default settings', () => { it('renders checkpoint with correct title, body, and breadcrumbs', () => { - render( - <> - - {targets} - , - ); + render(); expect(screen.getByRole('dialog', { name: 'Checkpoint 1' })).toBeInTheDocument(); expect(screen.getByText('Checkpoint 1')).toBeInTheDocument(); @@ -103,14 +107,7 @@ describe('', () => { }); it('onClick of advance button advances to next checkpoint', async () => { - const { rerender } = render( - <> - - {targets} - , - ); + const { rerender } = render(); // Verify the first Checkpoint has rendered expect(screen.getByRole('heading', { name: 'Checkpoint 1' })).toBeInTheDocument(); @@ -120,14 +117,7 @@ describe('', () => { await userEvent.click(advanceButton); }); - rerender( - <> - - {targets} - , - ); + rerender(); const heading = screen.getByRole('heading', { name: 'Checkpoint 2' }); @@ -136,14 +126,7 @@ describe('', () => { }); it('onClick of dismiss button disables tour', async () => { - render( - <> - - {targets} - , - ); + render(); // Verify a Checkpoint has rendered expect(screen.getByRole('dialog', { name: 'Checkpoint 1' })).toBeInTheDocument(); @@ -161,14 +144,7 @@ describe('', () => { }); it('onClick of end button disables tour', async () => { - const { rerender } = render( - <> - - {targets} - , - ); + const { rerender } = render(); // Verify a Checkpoint has rendered expect(screen.getByRole('dialog', { name: 'Checkpoint 1' })).toBeInTheDocument(); @@ -184,28 +160,14 @@ describe('', () => { await userEvent.click(advanceButton2); }); - rerender( - <> - - {targets} - , - ); + rerender(); const advanceButton3 = screen.getByRole('button', { name: 'Override advance' }); await act(async () => { await userEvent.click(advanceButton3); }); - rerender( - <> - - {targets} - , - ); + rerender(); // Click the end button const endButton = screen.getByRole('button', { name: 'End' }); @@ -220,14 +182,7 @@ describe('', () => { }); it('onClick of escape key disables tour', async () => { - render( - <> - - {targets} - , - ); + render(); // Verify a Checkpoint has rendered expect(screen.getByRole('dialog', { name: 'Checkpoint 1' })).toBeInTheDocument(); @@ -283,27 +238,13 @@ describe('', () => { ], }; it('renders correct checkpoint on index override', () => { - render( - <> - - {targets} - , - ); + render(); expect(screen.getByRole('dialog', { name: 'Checkpoint 3' })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: 'Checkpoint 3' })).toBeInTheDocument(); }); it('applies override for advanceButtonText', async () => { - const { rerender } = render( - <> - - {targets} - , - ); + const { rerender } = render(); expect(screen.getByRole('button', { name: 'Override advance' })).toBeInTheDocument(); const advanceButton = screen.getByRole('button', { name: 'Override advance' }); await act(async () => { @@ -312,37 +253,16 @@ describe('', () => { expect(screen.queryByRole('button', { name: 'Override advance' })).not.toBeInTheDocument(); expect(customOnAdvance).toHaveBeenCalledTimes(1); - rerender( - <> - - {targets} - , - ); + rerender(); expect(screen.getByText('Checkpoint 4')).toBeInTheDocument(); }); it('applies override for dismissButtonText', () => { - render( - <> - - {targets} - , - ); + render(); expect(screen.getByRole('button', { name: 'Override dismiss' })).toBeInTheDocument(); }); it('calls customHandleDismiss onClick of dismiss button', async () => { - render( - <> - - {targets} - , - ); + render(); const dismissButton = screen.getByRole('button', { name: 'Override dismiss' }); await act(async () => { await userEvent.click(dismissButton); @@ -351,27 +271,13 @@ describe('', () => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); it('calls customHandleOnEnd onClick of end button', async () => { - const { rerender } = render( - <> - - {targets} - , - ); + const { rerender } = render(); const advanceButton = screen.getByRole('button', { name: 'Override advance' }); await act(async () => { await userEvent.click(advanceButton); }); - rerender( - <> - - {targets} - , - ); + rerender(); expect(screen.getByText('Checkpoint 4')).toBeInTheDocument(); const endButton = screen.getByRole('button', { name: 'Override end' }); @@ -383,14 +289,7 @@ describe('', () => { expect(screen.queryByText('Checkpoint 4')).not.toBeInTheDocument(); }); it('calls onEscape on escape button key press', async () => { - render( - <> - - {targets} - , - ); + render(); expect(screen.getByText('Checkpoint 3')).toBeInTheDocument(); const container = screen.getByRole('dialog'); container.focus(); @@ -421,14 +320,7 @@ describe('', () => { ], }; - render( - <> - - {targets} - , - ); + render(); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); @@ -456,14 +348,7 @@ describe('', () => { ], }; - render( - <> - - {targets} - , - ); + render(); expect(screen.queryByRole('dialog', { name: 'Checkpoint 1' })).not.toBeInTheDocument(); expect(screen.getByRole('dialog', { name: 'Checkpoint 2' })).toBeInTheDocument(); @@ -491,14 +376,7 @@ describe('', () => { ], }; - render( - <> - - {targets} - , - ); + render(); expect(screen.getByText('Checkpoint 1')).toBeInTheDocument(); expect(screen.queryByText('Second enabled tour')).not.toBeInTheDocument(); @@ -507,14 +385,7 @@ describe('', () => { describe('disabled tour', () => { it('does not render', () => { - render( - <> - - {targets} - , - ); + render(); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); From ae63a66598d4fd04faf1b4c32e2971b1eadfff00 Mon Sep 17 00:00:00 2001 From: Kyrylo Hudym-Levkovych Date: Tue, 31 Oct 2023 12:30:05 +0200 Subject: [PATCH 2/2] fix: add fixes after review --- package.json | 2 +- src/ProductTour/Checkpoint.jsx | 5 +++-- src/i18n/messages/ar.json | 3 ++- src/i18n/messages/ca.json | 3 ++- src/i18n/messages/es_419.json | 3 ++- src/i18n/messages/es_AR.json | 3 ++- src/i18n/messages/es_ES.json | 3 ++- src/i18n/messages/fr.json | 3 ++- src/i18n/messages/he.json | 3 ++- src/i18n/messages/id.json | 3 ++- src/i18n/messages/it_IT.json | 3 ++- src/i18n/messages/ko_KR.json | 3 ++- src/i18n/messages/pl.json | 3 ++- src/i18n/messages/pt_BR.json | 3 ++- src/i18n/messages/pt_PT.json | 3 ++- src/i18n/messages/ru.json | 3 ++- src/i18n/messages/th.json | 3 ++- src/i18n/messages/tr_TR.json | 3 ++- src/i18n/messages/uk.json | 3 ++- src/i18n/messages/zh_CN.json | 3 ++- 20 files changed, 40 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 1afbc6ba1b..4cecab0003 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "example:start:with-theme": "npm run start:with-theme --workspace=example", "generate-changelog": "node generate-changelog.js", "i18n_compile": "formatjs compile-folder --format transifex ./src/i18n/messages ./src/i18n/messages", - "i18n_extract": "formatjs extract 'src/**/*.{jsx,js,tsx,ts}' --out-file ./src/i18n/transifex_input.json --format transifex", + "i18n_extract": "formatjs extract 'src/**/*.{jsx,js,tsx,ts}' --out-file ./src/i18n/transifex_input.json --ignore='**/*.d.ts' --format transifex", "type-check": "tsc --noEmit && tsc --project www --noEmit", "type-check:watch": "npm run type-check -- --watch", "build-types": "tsc --emitDeclarationOnly", diff --git a/src/ProductTour/Checkpoint.jsx b/src/ProductTour/Checkpoint.jsx index 00bdc277aa..9a08c0763e 100644 --- a/src/ProductTour/Checkpoint.jsx +++ b/src/ProductTour/Checkpoint.jsx @@ -98,8 +98,9 @@ const Checkpoint = React.forwardRef(({ > diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index b9c148ba3e..da2ed734cf 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "رفع {filename} جارٍ.", "pgn.FormAutosuggest.iconButtonClosed": "إغلاق قائمة الخيارات", "pgn.FormAutosuggest.iconButtonOpened": "فتح قائمة الخيارات", - "pgn.Toast.closeLabel": "إغلاق " + "pgn.Toast.closeLabel": "إغلاق ", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/ca.json b/src/i18n/messages/ca.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/ca.json +++ b/src/i18n/messages/ca.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/es_419.json b/src/i18n/messages/es_419.json index 9cd5769e52..29c5b77cb0 100644 --- a/src/i18n/messages/es_419.json +++ b/src/i18n/messages/es_419.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Subiendo {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones", "pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones", - "pgn.Toast.closeLabel": "Cerrar" + "pgn.Toast.closeLabel": "Cerrar", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/es_AR.json b/src/i18n/messages/es_AR.json index ec8c548657..638e6af70e 100644 --- a/src/i18n/messages/es_AR.json +++ b/src/i18n/messages/es_AR.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Subiendo {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones", "pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones", - "pgn.Toast.closeLabel": "Cerrar" + "pgn.Toast.closeLabel": "Cerrar", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/es_ES.json b/src/i18n/messages/es_ES.json index f391f7852b..81c8e6df9e 100644 --- a/src/i18n/messages/es_ES.json +++ b/src/i18n/messages/es_ES.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Subiendo {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Cerrar el menú de opciones", "pgn.FormAutosuggest.iconButtonOpened": "Abre el menú de opciones", - "pgn.Toast.closeLabel": "Cerrar" + "pgn.Toast.closeLabel": "Cerrar", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/it_IT.json b/src/i18n/messages/it_IT.json index dd46ce0e6a..ff3baf38c8 100644 --- a/src/i18n/messages/it_IT.json +++ b/src/i18n/messages/it_IT.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Caricamento {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Chiudi" + "pgn.Toast.closeLabel": "Chiudi", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/ko_KR.json b/src/i18n/messages/ko_KR.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/ko_KR.json +++ b/src/i18n/messages/ko_KR.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index f414d8999a..b23933a642 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Zamknij" + "pgn.Toast.closeLabel": "Zamknij", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/pt_BR.json b/src/i18n/messages/pt_BR.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/pt_BR.json +++ b/src/i18n/messages/pt_BR.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/pt_PT.json b/src/i18n/messages/pt_PT.json index dddb2d9088..534c64b645 100644 --- a/src/i18n/messages/pt_PT.json +++ b/src/i18n/messages/pt_PT.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Carregando {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Fechar o menu de opções", "pgn.FormAutosuggest.iconButtonOpened": "Abrir o menu de opções", - "pgn.Toast.closeLabel": "Fechar" + "pgn.Toast.closeLabel": "Fechar", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/tr_TR.json b/src/i18n/messages/tr_TR.json index f50da7eb35..c29565d297 100644 --- a/src/i18n/messages/tr_TR.json +++ b/src/i18n/messages/tr_TR.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "{filename} yükleniyor.", "pgn.FormAutosuggest.iconButtonClosed": "Seçenekler menüsünü kapat", "pgn.FormAutosuggest.iconButtonOpened": "Seçenekler menüsünü aç", - "pgn.Toast.closeLabel": "Kapat" + "pgn.Toast.closeLabel": "Kapat", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/uk.json b/src/i18n/messages/uk.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/uk.json +++ b/src/i18n/messages/uk.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file diff --git a/src/i18n/messages/zh_CN.json b/src/i18n/messages/zh_CN.json index 431506919f..022a108c1c 100644 --- a/src/i18n/messages/zh_CN.json +++ b/src/i18n/messages/zh_CN.json @@ -27,5 +27,6 @@ "pgn.Dropzone.UploadProgress.uploadLabel": "Uploading {filename}.", "pgn.FormAutosuggest.iconButtonClosed": "Close the options menu", "pgn.FormAutosuggest.iconButtonOpened": "Open the options menu", - "pgn.Toast.closeLabel": "Close" + "pgn.Toast.closeLabel": "Close", + "pgn.ProductTour.Checkpoint.position-text": "Top of step {step}" } \ No newline at end of file