diff --git a/assets/localization/en.json b/assets/localization/en.json index b6364017e6..675cc44c20 100644 --- a/assets/localization/en.json +++ b/assets/localization/en.json @@ -1,21 +1,48 @@ { - "%product_name%": "Platform.Bible", "%about_versionLabel_format%": "Version: {version}", "%about_licenseLabel_format%": "License: {license}", + "%downloadUpdateProjectTab_aria_downloadable%": "downloadable projects", + "%downloadUpdateProjectTab_aria_downloaded%": "downloaded projects", + "%downloadUpdateProjectTab_button_delete%": "Delete", + "%downloadUpdateProjectTab_listHeader_downloadable%": "Downloadable Projects", + "%downloadUpdateProjectTab_listHeader_downloaded%": "Downloaded Projects", + "%downloadUpdateProjectTab_title_downloadUpdate%": "Download/Update Project", + "%general_button_submit%": "Submit", + "%general_cancel%": "Cancel", "%insertNote%": "Insert Note", "%general_error_title%": "Error", - "%mainMenu_about%": "About Platform.Bible", - "%mainMenu_downloadSlashInstallResources%": "Download/Install Resources", - "%mainMenu_downloadSlashUpdateProject%": "Download/Update Project", - "%mainMenu_exit%": "Exit", + "%general_loading%": "Loading", + "%general_run%": "Run", "%mainMenu_help%": "Help", "%mainMenu_layout%": "Layout", + "%mainMenu_about%": "About Platform.Bible", + "%mainMenu_downloadInstallResources%": "Download/Install Resources", + "%mainMenu_exit%": "Exit", "%mainMenu_openProject%": "Open Project", - "%mainMenu_project%": "Project", "%mainMenu_settings%": "Settings", "%mainMenu_visitSupportBible%": "Visit Support.Bible", + "%mainMenu_project%": "Project", "%mainMenu_window%": "Window", - "%some_localization_key%": "This is the English text for %some_localization_key%.", + "%menuItemName_wordList%": "Word List", + "%product_name%": "Platform.Bible", + "%project_full_name_missing%": "*Name Missing*", + "%project_language_missing%": "*Language Missing*", + "%project_name_missing%": "_NoName_", + "%project_settings_platform_group1_description%": "Project settings pertaining to the software overall", + "%project_settings_platform_fullName_label%": "Project Full Name", + "%project_settings_platform_group1_label%": "Platform Settings", + "%project_settings_platform_isEditable_label%": "Is Editable", + "%project_settings_platform_isEditable_description%": "Whether this project is editable. A project that is not editable is sometimes called a resource.", + "%project_settings_platform_language_label%": "Project Primary Language", + "%project_settings_platform_name_label%": "Project Short Name", + "%selectBooks_title_selectBooks%": "Select Books", + "%selectMultipleProjects_title_selectProjects%": "Select Projects", + "%selectProject_title%": "Select Project", + "%settings_defaultMessage_loadingSetting%": "Loading setting", + "%settings_defaultSearchText_searchProject%": "Search Project Settings...", + "%settings_defaultSearchText_searchUserSettings%": "Search User Settings...", + "%settings_title_projectSettings%": "Project Settings", + "%settings_title_userSettings%": "User Settings", "%submitButton%": "Submit", "%scrollGroup_undefined%": "Ø", "%scrollGroup_0%": "A", @@ -23,6 +50,11 @@ "%scrollGroup_2%": "C", "%scrollGroup_3%": "D", "%scrollGroup_4%": "E", + "%some_localization_key%": "This is the English text for %some_localization_key%.", + "%tab_aria_tab%": "Tab", + "%tab_title_error%": "Error", + "%tab_title_unknown%": "Unknown", + "%webView_defaultTitle_webView%": "Web View", "%webView_edit%": "Edit", "%webView_openProjectSettings%": "Open Project Settings", "%webView_project%": "Project", @@ -38,14 +70,5 @@ "%settings_platform_paratextDataLastRegistryDataCachedTimes_label%": "ParatextData Last Registry Data Cached Times", "%settings_platform_comments_enabled_label%": "Comments enabled (experimental)", "%settings_platform_comments_enabled_description%": "Enabled reading and writing comments in projects. This is an experimental feature.", - "%project_settings_platform_group1_label%": "Platform Settings", - "%project_settings_platform_group1_description%": "Project settings pertaining to the software overall", - "%project_settings_platform_name_label%": "Project Short Name", - "%project_name_missing%": "_NoName_", - "%project_settings_platform_fullName_label%": "Project Full Name", - "%project_full_name_missing%": "*Name Missing*", - "%project_language_missing%": "*Language Missing*", - "%project_settings_platform_language_label%": "Project Primary Language", - "%project_settings_platform_isEditable_label%": "Is Editable", - "%project_settings_platform_isEditable_description%": "Whether this project is editable. A project that is not editable is sometimes called a resource." + "%webView_title_type_formatString%": "{type} {defaultTitle}" } diff --git a/assets/localization/es.json b/assets/localization/es.json index c6ee1ad7ec..12f9f22a06 100644 --- a/assets/localization/es.json +++ b/assets/localization/es.json @@ -2,7 +2,7 @@ "%about_versionLabel_format%": "Versión: {version}", "%about_licenseLabel_format%": "Licencia: {license}", "%insertNote%": "Insertar Nota", - "%mainMenu_aboutPlatformBible%": "Acerca de Platform.Bible", + "%mainMenu_about%": "Acerca de Platform.Bible", "%mainMenu_downloadSlashInstallResources%": "Descargar/instalar recursos", "%mainMenu_downloadSlashUpdateProject%": "Descargar/actualizar proyecto", "%mainMenu_exit%": "Salir", @@ -20,8 +20,8 @@ "%webView_project%": "Proyecto", "%webView_projectAssignmentsAndProgress%": "Asignaciones y progreso", "%wordList%": "Lista de palabras", - "%settings_platform_group1_label%": "Configuraciones de Platform", "%settings_platform_group1_description%": "Configuraciones generales del software fundamental", - "%settings_platform_interfaceLanguage_label%": "Idioma de interfaz", - "%settings_platform_interfaceLanguage_description%": "Lista de locales usados al localizar la interfaz en orden descendente de prioridad. 'en' (inglés) siempre se añade al final, de modo que si no se encuentra una traducción en ninguno de los idiomas especificados, aparecerá en inglés." + "%settings_platform_interfaceLanguage_description%": "Lista de locales usados al localizar la interfaz en orden descendente de prioridad. 'en' (inglés) siempre se añade al final, de modo que si no se encuentra una traducción en ninguno de los idiomas especificados, aparecerá en inglés.", + "%settings_platform_label_group1%": "Configuraciones de Platform", + "%settings_platform_interfaceLanguage_label%": "Idioma de interfaz" } diff --git a/assets/localization/fr.json b/assets/localization/fr.json index bd3147e528..4b7f3d5626 100644 --- a/assets/localization/fr.json +++ b/assets/localization/fr.json @@ -1,5 +1,5 @@ { "%some_localization_key%": "Ceci est le texte en français pour %some_localization_key%.", - "%submitButton%": "Soumettre", + "%general_button_submit%": "Soumettre", "%Book.Gen%": "Genèse" } diff --git a/extensions/src/hello-world/contributions/localizedStrings.json b/extensions/src/hello-world/contributions/localizedStrings.json index 2e907918ab..046659d6d0 100644 --- a/extensions/src/hello-world/contributions/localizedStrings.json +++ b/extensions/src/hello-world/contributions/localizedStrings.json @@ -13,10 +13,36 @@ "%mainMenu_openHelloWorldProject%": "Open Hello World Project", "%mainMenu_createNewHelloWorldProject%": "Create New Hello World Project", "%mainMenu_deleteHelloWorldProject%": "Delete Hello World Project", - "%helloWorld_webViewMenu_project%": "Project", - "%helloWorld_webViewMenu_view%": "View", + "%helloWorld_greetingLoading%": "Greeting loading", + "%helloWorld_delete%": "Delete", + "%helloWorld_frenchLocalizationSubmit%": "French localization of Submit Button", + "%helloWorld_helloWorld%": "Hello World", + "%helloWorld_listOfSelectedIds%": "List of Selected Project Id(s)", + "%helloWorld_logo%": "Hello World Logo", + "%helloWorld_none%": "None", + "%helloWorld_openResourceViewer%": "Open in Resource Viewer", + "%helloWorld_openScriptureEditor%": "Open in Scripture Editor", + "%helloWorld_option1%": "Option 1", + "%helloWorld_option2%": "Option 2", + "%helloWorld_react%": "React", + "%helloWorld_select_project%": "Select Project", + "%helloWorld_select_projects%": "Select Projects", + "%helloWorld_selected_project%": "Selected Project", + "%helloWorld_table_id%": "ID", + "%helloWorld_table_subtitle%": "Subtitle", + "%helloWorld_table_title%": "Title", + "%helloWorld_testMe%": "Test Me", + "%helloWorld_throw_test_exception%": "Throw test exception", "%helloWorld_webViewMenu_deleteProject%": "Delete this Project", "%helloWorld_webViewMenu_openViewer%": "Open Viewer", + "%helloWorld_webViewMenu_project%": "Project", + "%helloWorld_webViewMenu_view%": "View", + "%helloWorld_loadingLatest%": "Loading latest Scripture text...", + "%helloWorld_loadingVerse%": "Loading Verse", + "%helloWorld_selectProject_prompt%": "Please select a Scripture project for Hello World WebView: (Render", + "%helloWorld_selectProject_title%": "Select Hello World Project", + "%helloWorld_selectProjects_prompt%": "Please select one or more Scripture projects for Hello World WebView:", + "%helloWorld_selectProjects_title%": "Select List of Hello World Projects", "%settings_hello_world_group1_label%": "Hello World Settings", "%settings_hello_world_personName_label%": "Selected Person's Name on Hello World Web View", "%project_settings_helloWorld_group1_label%": "Hello World Project Settings", diff --git a/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx b/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx index 1082d966d8..be5b95a4b1 100644 --- a/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx +++ b/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx @@ -48,6 +48,89 @@ globalThis.webViewComponent = function HelloWorld({ [scrRef], ); + const deleteKey = '%helloWorld_delete%'; + const frenchLocalizationSubmit = '%helloWorld_frenchLocalizationSubmit%'; + const greetingLoading = '%helloWorld_greetingLoading%'; + const helloWorld = '%helloWorld_helloWorld%'; + const listOfSelectedIds = '%helloWorld_listOfSelectedIds%'; + const logoKey = '%helloWorld_logo%'; + const noneKey = '%helloWorld_none%'; + const openResourceViewer = '%helloWorld_openResourceViewer%'; + const openScriptureEditor = '%helloWorld_openScriptureEditor%'; + const option1 = '%helloWorld_option1%'; + const option2 = '%helloWorld_option2%'; + const quickVerseLoadingLatest = '%helloWorld_loadingLatest%'; + const reactKey = '%helloWorld_react%'; + const scriptureLoadingVerse = '%helloWorld_loadingVerse%'; + const selectedProjectKey = '%helloWorld_selected_project%'; + const selectProjectKey = '%helloWorld_select_project%'; + const selectProjectPrompt = '%helloWorld_selectProject_prompt%'; + const selectProjectsKey = '%helloWorld_select_projects%'; + const selectProjectsPrompt = '%helloWorld_selectProjects_prompt%'; + const selectProjectsTitle = '%helloWorld_selectProjects_title%'; + const selectProjectTitle = '%helloWorld_selectProject_title%'; + const submit = '%general_button_submit%'; + const testException = '%helloWorld_throw_test_exception%'; + const testMe = '%helloWorld_testMe%'; + + const [localizedStrings] = useLocalizedStrings( + useMemo( + () => [ + deleteKey, + frenchLocalizationSubmit, + greetingLoading, + helloWorld, + listOfSelectedIds, + logoKey, + noneKey, + openResourceViewer, + openScriptureEditor, + option1, + option2, + quickVerseLoadingLatest, + reactKey, + scriptureLoadingVerse, + selectedProjectKey, + selectProjectKey, + selectProjectPrompt, + selectProjectsKey, + selectProjectsPrompt, + selectProjectsTitle, + selectProjectTitle, + submit, + testException, + testMe, + ], + [], + ), + useMemo(() => ['fr', 'en'], []), + ); + + const localizedDelete = localizedStrings[deleteKey]; + const localizedFrenchLocalizationSubmit = localizedStrings[frenchLocalizationSubmit]; + const localizedGreetingLoading = localizedStrings[greetingLoading]; + const localizedHelloWorld = localizedStrings[helloWorld]; + const localizedListOfSelectedIds = localizedStrings[listOfSelectedIds]; + const localizedLogo = localizedStrings[logoKey]; + const localizedNone = localizedStrings[noneKey]; + const localizedOpenResourceViewer = localizedStrings[openResourceViewer]; + const localizedOpenScriptureEditor = localizedStrings[openScriptureEditor]; + const localizedOption1 = localizedStrings[option1]; + const localizedOption2 = localizedStrings[option2]; + const localizedQuickVerseLoadingLatest = localizedStrings[quickVerseLoadingLatest]; + const localizedReact = localizedStrings[reactKey]; + const localizedScriptureLoadingVerse = localizedStrings[scriptureLoadingVerse]; + const localizedSelectedProject = localizedStrings[selectedProjectKey]; + const localizedSelectProject = localizedStrings[selectProjectKey]; + const localizedSelectProjectPrompt = localizedStrings[selectProjectPrompt]; + const localizedSelectProjects = localizedStrings[selectProjectsKey]; + const localizedSelectProjectsPrompt = localizedStrings[selectProjectsPrompt]; + const localizedSelectProjectsTitle = localizedStrings[selectProjectsTitle]; + const localizedSelectProjectTitle = localizedStrings[selectProjectTitle]; + const localizedSubmit = localizedStrings[submit]; + const localizedTestException = localizedStrings[testException]; + const localizedTestMe = localizedStrings[testMe]; + // Update the clicks when we are informed helloWorld has been run useEvent( papi.network.getNetworkEvent('helloWorld.onHelloWorld'), @@ -61,8 +144,8 @@ globalThis.webViewComponent = function HelloWorld({ useEffect(() => { logger.debug(`Hello World WebView previous title: ${title}`); - updateWebViewDefinition({ title: `Hello World ${clicks}` }); - }, [title, updateWebViewDefinition, clicks]); + updateWebViewDefinition({ title: `${localizedHelloWorld} ${clicks}` }); + }, [title, updateWebViewDefinition, localizedHelloWorld, clicks]); const currentRender = useRef(-1); currentRender.current += 1; @@ -72,9 +155,9 @@ globalThis.webViewComponent = function HelloWorld({ // This is intentionally not a stable reference like `useMemo` or something because we are // testing below to make sure `useDialogCallback` returns the same callback every time { - prompt: `Please select a Scripture project for Hello World WebView: (Render ${currentRender.current})`, + prompt: `${localizedSelectProjectPrompt} ${currentRender.current})`, iconUrl: 'papi-extension://helloWorld/assets/offline.svg', - title: 'Select Hello World Project', + title: localizedSelectProjectTitle, maximumOpenDialogs: 2, // Test ref parameter properly getting latest value currentRender: currentRender.current, @@ -111,7 +194,7 @@ globalThis.webViewComponent = function HelloWorld({ const [latestVerseText] = useData('quickVerse.quickVerse').Verse( 'latest', - 'Loading latest Scripture text...', + localizedQuickVerseLoadingLatest, ); const [projects, setProjects] = useWebViewState('projects', []); @@ -120,13 +203,13 @@ globalThis.webViewComponent = function HelloWorld({ 'platform.selectMultipleProjects', useMemo( () => ({ - prompt: 'Please select one or more Scripture projects for Hello World WebView:', + prompt: localizedSelectProjectsPrompt, iconUrl: 'papi-extension://helloWorld/assets/offline.svg', - title: 'Select List of Hello World Projects', + title: localizedSelectProjectsTitle, selectedProjectIds: projects, includeProjectInterfaces: ['platformScripture.USFM_Verse'], }), - [projects], + [localizedSelectProjectsPrompt, localizedSelectProjectsTitle, projects], ), useCallback( (selectedProjects) => { @@ -163,34 +246,33 @@ globalThis.webViewComponent = function HelloWorld({ const peopleDataProvider = useDataProvider('helloSomeone.people'); - const [personGreeting] = useData('helloSomeone.people').Greeting(name, 'Greeting loading'); + const [personGreeting] = useData('helloSomeone.people').Greeting(name, localizedGreetingLoading); const [personAge] = useData('helloSomeone.people').Age(name, -1); const [currentProjectVerse] = useProjectData( 'platformScripture.USFM_Verse', projectId ?? undefined, - ).VerseUSFM(verseRef, 'Loading Verse'); + ).VerseUSFM(verseRef, localizedScriptureLoadingVerse); const helloWorldProjectSettings = useHelloWorldProjectSettings(projectId); const { headerStyle } = helloWorldProjectSettings; - const [localizedStrings] = useLocalizedStrings( - useMemo(() => ['%submitButton%'], []), - useMemo(() => ['fr', 'en'], []), - ); - const localizedString = localizedStrings['%submitButton%']; + const genericComboBoxOptions = useMemo( + () => [localizedOption1, localizedOption2], + [localizedOption1, localizedOption2], + ); return (
- Hello World React + {localizedHelloWorld} {localizedReact} {/** * Note: `Logo` here is inlined into this code as a `data:` url. This is here simply for * demonstration purposes. Inlining as a `data:` url is generally not recommended. Rather, it is * generally better to use `papi-extension:` to avoid unnecessary bloat */} - Hello World Logo + {localizedLogo}
@@ -211,21 +293,25 @@ globalThis.webViewComponent = function HelloWorld({ throw new Error(`${NAME} test exception!`); }} > - Throw test exception + {localizedTestException}
{latestVerseText}
setName(e.target.value)} /> - +
{personGreeting}
{personAge}

-
Selected Project: {projectId ?? 'None'}
- + {localizedSelectedProject}: {projectId ?? localizedNone} +
+
+

{verseRef.toString()}

{currentProjectVerse}
-

List of Selected Project Id(s):

-
{(projects.length > 0 ? projects : ['None']).join(', ')}
+

{localizedListOfSelectedIds}:

+
{(projects.length > 0 ? projects : [{ localizedNone }]).join(', ')}
- +

- + {/* no label available */} {/* no label available */} - + {/* no label available */} setScrRef(newScrRef)} />
-

French localization of Submit Button:

-
{localizedString}
+

{localizedFrenchLocalizationSubmit}:

+
{localizedSubmit}
); diff --git a/extensions/src/platform-scripture-editor/contributions/localizedStrings.json b/extensions/src/platform-scripture-editor/contributions/localizedStrings.json index 64a5057f36..f9fca13a24 100644 --- a/extensions/src/platform-scripture-editor/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture-editor/contributions/localizedStrings.json @@ -17,6 +17,10 @@ "%webView_platformScriptureEditor_showCheckResults%": "Show Check Results...", "%webView_platformScriptureEditor_publisherInfo%": "Publisher Info", "%webView_platformScriptureEditor_copyrightInfo%": "Copyright Info", + "%webView_platformScriptureEditor_title_editable_indicator%": "(Editable)", + "%webView_platformScriptureEditor_title_readonly_no_project%": "Resource Viewer", + "%webView_platformScriptureEditor_title_editable_no_project%": "Scripture Editor", + "%webView_platformScriptureEditor_title_format%": "{projectId} {editable}", "%platformScriptureEditor_dialog_openResourceViewer_title%": "Open Resource Viewer", "%platformScriptureEditor_dialog_openResourceViewer_prompt%": "Choose a resource to open:", "%platformScriptureEditor_dialog_openScriptureEditor_title%": "Open Scripture Editor", diff --git a/extensions/src/platform-scripture-editor/src/main.ts b/extensions/src/platform-scripture-editor/src/main.ts index 930e57450d..027f812d31 100644 --- a/extensions/src/platform-scripture-editor/src/main.ts +++ b/extensions/src/platform-scripture-editor/src/main.ts @@ -6,18 +6,31 @@ import type { SavedWebViewDefinition, WebViewDefinition, } from '@papi/core'; +import { formatReplacementString, LanguageStrings } from 'platform-bible-utils'; import platformScriptureEditorWebView from './platform-scripture-editor.web-view?inline'; import platformScriptureEditorWebViewStyles from './platform-scripture-editor.web-view.scss?inline'; logger.info('Scripture Editor is importing!'); const scriptureEditorWebViewType = 'platformScriptureEditor.react'; +const projectIdTitleFormatStr = '%webView_platformScriptureEditor_title_format%'; +const editable = '%webView_platformScriptureEditor_title_editable_indicator%'; +const resourceViewer = '%webView_platformScriptureEditor_title_readonly_no_project%'; +const scriptureEditor = '%webView_platformScriptureEditor_title_editable_no_project%'; interface PlatformScriptureEditorOptions extends GetWebViewOptions { projectId: string | undefined; isReadOnly: boolean; } +async function getLocalizations(): Promise { + const localizationData = await papi.localization.getLocalizedStrings({ + localizeKeys: [editable, projectIdTitleFormatStr, resourceViewer, scriptureEditor], + locales: ['en'], + }); + return localizationData; +} + /** Temporary function to manually control `isReadOnly`. Registered as a command handler. */ async function openPlatformScriptureEditor( projectId: string | undefined, @@ -103,16 +116,23 @@ const scriptureEditorWebViewProvider: IWebViewProvider = { `${scriptureEditorWebViewType} provider received request to provide a ${savedWebView.webViewType} web view`, ); + const localizedStrings = await getLocalizations(); + const localizedProjectIdTitleFormatStr = localizedStrings[projectIdTitleFormatStr]; + const localizedEditable = localizedStrings[editable]; + const localizedResourceViewer = localizedStrings[resourceViewer]; + const localizedScriptureEditor = localizedStrings[scriptureEditor]; + // We know that the projectId (if present in the state) will be a string. const projectId = getWebViewOptions.projectId || savedWebView.projectId || undefined; const isReadOnly = getWebViewOptions.isReadOnly || savedWebView.state?.isReadOnly; let title = ''; if (projectId) { const pdp = await papi.projectDataProviders.get('platform.base', projectId); - title = `${ - (await pdp.getSetting('platform.name')) ?? projectId - }${isReadOnly ? '' : ' (Editable)'}`; - } else title = isReadOnly ? 'Resource Viewer' : 'Scripture Editor'; + title = formatReplacementString(localizedProjectIdTitleFormatStr, { + projectId: (await pdp.getSetting('platform.name')) ?? projectId, + editable: isReadOnly ? '' : localizedEditable, + }); + } else title = isReadOnly ? localizedResourceViewer : localizedScriptureEditor; return { title, diff --git a/lib/papi-dts/papi.d.ts b/lib/papi-dts/papi.d.ts index 4e88b0feab..20e4b41689 100644 --- a/lib/papi-dts/papi.d.ts +++ b/lib/papi-dts/papi.d.ts @@ -120,7 +120,7 @@ declare module 'shared/services/scroll-group.service-model' { } declare module 'shared/models/web-view.model' { import type { ScrollGroupScrRef } from 'shared/services/scroll-group.service-model'; - import { ScriptureReference, ScrollGroupId } from 'platform-bible-utils'; + import { ScriptureReference, ScrollGroupId, LocalizeKey } from 'platform-bible-utils'; /** The type of code that defines a webview's content */ export enum WebViewContentType { /** @@ -155,8 +155,11 @@ declare module 'shared/models/web-view.model' { * Defaults to the software's standard logo. */ iconUrl?: string; - /** Name of the tab for the WebView */ - title?: string; + /** + * Name of the tab (or a localizeKey for the name that will automatically be localized) for the + * WebView + */ + title?: string | LocalizeKey; /** Tooltip that is shown when hovering over the webview title */ tooltip?: string; /** @@ -2659,6 +2662,7 @@ declare module 'shared/models/docking-framework.model' { import { MutableRefObject, ReactNode } from 'react'; import { DockLayout, DropDirection, LayoutBase } from 'rc-dock'; import { WebViewDefinition, WebViewDefinitionUpdateInfo } from 'shared/models/web-view.model'; + import { LocalizeKey } from 'platform-bible-utils'; /** * Saved information used to recreate a tab. * @@ -2689,8 +2693,11 @@ declare module 'shared/models/docking-framework.model' { * Defaults to the software's standard logo. */ tabIconUrl?: string; - /** Text to show on the title bar of the tab */ - tabTitle: string; + /** + * Text to show (or a localizeKey that will automatically be localized) on the title bar of the + * tab + */ + tabTitle: string | LocalizeKey; /** Text to show when hovering over the title bar of the tab */ tabTooltip?: string; /** Content to show inside the tab. */ diff --git a/lib/platform-bible-react/dist/index.d.ts b/lib/platform-bible-react/dist/index.d.ts index 4f0332f8cc..57d3c4eb87 100644 --- a/lib/platform-bible-react/dist/index.d.ts +++ b/lib/platform-bible-react/dist/index.d.ts @@ -978,7 +978,7 @@ export declare const CardTitle: React$1.ForwardRefExoticComponent & React$1.RefAttributes>; export declare const CardContent: React$1.ForwardRefExoticComponent & React$1.RefAttributes>; export declare const CardFooter: React$1.ForwardRefExoticComponent & React$1.RefAttributes>; -export declare const Checkbox: React$1.ForwardRefExoticComponent, "ref"> & React$1.RefAttributes>; +export declare const Checkbox: React$1.ForwardRefExoticComponent, "ref"> & React$1.RefAttributes>; export declare const DropdownMenu: React$1.FC; export declare const DropdownMenuTrigger: React$1.ForwardRefExoticComponent>; export declare const DropdownMenuGroup: React$1.ForwardRefExoticComponent>; diff --git a/lib/platform-bible-react/src/components/mui/gridMenu.component.render.test.tsx b/lib/platform-bible-react/src/components/mui/gridMenu.component.render.test.tsx index 6cdb6e731f..88bca84791 100644 --- a/lib/platform-bible-react/src/components/mui/gridMenu.component.render.test.tsx +++ b/lib/platform-bible-react/src/components/mui/gridMenu.component.render.test.tsx @@ -47,9 +47,9 @@ describe('GridMenu renders', () => { html: screen.getByRole('menu', { name: 'paratext.paratext' }), label: '%mainMenu_Paratext%', }, - { html: screen.getByRole('menu', { name: 'platform.window' }), label: '%mainMenu_Window%' }, - { html: screen.getByRole('menu', { name: 'platform.layout' }), label: '%mainMenu_Layout%' }, - { html: screen.getByRole('menu', { name: 'platform.help' }), label: '%mainMenu_Help%' }, + { html: screen.getByRole('menu', { name: 'platform.window' }), label: '%mainMenu_window%' }, + { html: screen.getByRole('menu', { name: 'platform.layout' }), label: '%mainMenu_layout%' }, + { html: screen.getByRole('menu', { name: 'platform.help' }), label: '%mainMenu_help%' }, ]; expectedColumns.forEach((column) => { diff --git a/lib/platform-bible-react/src/components/mui/sample.composed.full.menu.json b/lib/platform-bible-react/src/components/mui/sample.composed.full.menu.json index daab549fb1..2e064a6188 100644 --- a/lib/platform-bible-react/src/components/mui/sample.composed.full.menu.json +++ b/lib/platform-bible-react/src/components/mui/sample.composed.full.menu.json @@ -2,9 +2,9 @@ "mainMenu": { "columns": { "paratext.paratext": { "label": "%mainMenu_Paratext%", "order": 0 }, - "platform.window": { "label": "%mainMenu_Window%", "order": 1 }, - "platform.layout": { "label": "%mainMenu_Layout%", "order": 2 }, - "platform.help": { "label": "%mainMenu_Help%", "order": 3, "isExtensible": true }, + "platform.window": { "label": "%mainMenu_window%", "order": 1 }, + "platform.layout": { "label": "%mainMenu_layout%", "order": 2 }, + "platform.help": { "label": "%mainMenu_help%", "order": 3, "isExtensible": true }, "isExtensible": true }, "groups": { @@ -92,8 +92,8 @@ }, "defaultWebViewTopMenu": { "columns": { - "platform.project": { "label": "%webView_Project%", "order": 1 }, - "platform.edit": { "label": "%webView_Edit%", "order": 2, "isExtensible": true } + "platform.project": { "label": "%webView_project%", "order": 1 }, + "platform.edit": { "label": "%webView_edit%", "order": 2, "isExtensible": true } }, "groups": { "platform.projectTop": { "column": "platform.project", "order": 1 }, @@ -134,7 +134,7 @@ "command": "platform.insertNote" }, { - "label": "%wordList%", + "label": "%menuItemName_wordList%", "localizeNotes": "Web view context menu > Word list...", "group": "platform.wordList", "order": 1, diff --git a/src/extension-host/data/menu.data.json b/src/extension-host/data/menu.data.json index ec38ace803..9f104526f4 100644 --- a/src/extension-host/data/menu.data.json +++ b/src/extension-host/data/menu.data.json @@ -27,7 +27,7 @@ }, "items": [ { - "label": "%mainMenu_downloadSlashInstallResources%", + "label": "%mainMenu_downloadInstallResources%", "localizeNotes": "Application main menu > Project > Download/Install Resources", "group": "platform.projectResources", "order": 1, @@ -56,7 +56,7 @@ }, { "label": "%mainMenu_about%", - "localizeNotes": "Application main menu > Help > About (Platform.Bible or Software white-label name)", + "localizeNotes": "Application main menu > Help > About Platform.Bible", "group": "platform.helpMisc", "order": 2, "command": "platform.about" @@ -107,7 +107,7 @@ "command": "platform.insertNote" }, { - "label": "%wordList%", + "label": "%menuItemName_wordList%", "localizeNotes": "Web view context menu > Word list...", "group": "platform.wordList", "order": 1, diff --git a/src/extension-host/services/localization.service-host.test.ts b/src/extension-host/services/localization.service-host.test.ts index 7f1686d504..abc0522fa0 100644 --- a/src/extension-host/services/localization.service-host.test.ts +++ b/src/extension-host/services/localization.service-host.test.ts @@ -6,11 +6,11 @@ import { LocalizeKey } from 'platform-bible-utils'; const MOCK_FILES: { [uri: string]: string } = { 'resources://assets/localization/en.json': `{ "%some_localization_key%": "This is the English text for %some_localization_key%.", - "%submitButton%": "Submit" + "%general_button_submit%": "Submit" }`, 'resources://assets/localization/fr.json': `{ "%some_localization_key%": "Ceci est le texte en français pour %some_localization_key%.", - "%submitButton%": "Soumettre" + "%general_button_submit%": "Soumettre" }`, 'resources://assets/localization/metadata.json': `{ "%yes%": { @@ -71,7 +71,7 @@ afterEach(() => { }); test('Correct localized value returned to match single localizeKey', async () => { - const LOCALIZE_KEY = '%submitButton%'; + const LOCALIZE_KEY = '%general_button_submit%'; const response = await localizationDataProviderEngine.getLocalizedString({ localizeKey: LOCALIZE_KEY, locales: ['fr'], @@ -80,13 +80,13 @@ test('Correct localized value returned to match single localizeKey', async () => }); test('Correct localized values returned to match array of localizeKeys', async () => { - const LOCALIZE_KEYS: LocalizeKey[] = ['%submitButton%', '%some_localization_key%']; + const LOCALIZE_KEYS: LocalizeKey[] = ['%general_button_submit%', '%some_localization_key%']; const response = await localizationDataProviderEngine.getLocalizedStrings({ localizeKeys: LOCALIZE_KEYS, locales: ['fr'], }); expect(response).toEqual({ - '%submitButton%': 'Soumettre', + '%general_button_submit%': 'Soumettre', '%some_localization_key%': 'Ceci est le texte en français pour %some_localization_key%.', }); }); @@ -124,14 +124,14 @@ test('Keys returned with localizeKeys that do not exist', async () => { }); test('Strings and keys returned with localizeKeys where one exists but the other does not', async () => { - const LOCALIZE_KEYS: LocalizeKey[] = ['%submitButton%', '%the_wrong_key%']; + const LOCALIZE_KEYS: LocalizeKey[] = ['%general_button_submit%', '%the_wrong_key%']; expect( await localizationDataProviderEngine.getLocalizedStrings({ localizeKeys: LOCALIZE_KEYS, locales: ['fr'], }), ).toEqual({ - '%submitButton%': 'Soumettre', + '%general_button_submit%': 'Soumettre', '%the_wrong_key%': '%the_wrong_key%', }); expect(logger.warn).toHaveBeenCalledWith( @@ -140,7 +140,7 @@ test('Strings and keys returned with localizeKeys where one exists but the other }); test('Error returned with localizeKey and incorrectly formatted language code', async () => { - const LOCALIZE_KEY = '%submitButton%'; // irrelevant because it will throw for language code before it accesses key/value pairs + const LOCALIZE_KEY = '%general_button_submit%'; // irrelevant because it will throw for language code before it accesses key/value pairs await expect( localizationDataProviderEngine.getLocalizedString({ localizeKey: LOCALIZE_KEY, @@ -153,7 +153,7 @@ test('Error returned with localizeKey and incorrectly formatted language code', }); test('Error returned with localizeKeys and incorrect language code', async () => { - const LOCALIZE_KEYS: LocalizeKey[] = ['%submitButton%', '%some_localization_key%']; // irrelevant because it will throw for language code before it accesses key/value pairs + const LOCALIZE_KEYS: LocalizeKey[] = ['%general_button_submit%', '%some_localization_key%']; // irrelevant because it will throw for language code before it accesses key/value pairs await expect( localizationDataProviderEngine.getLocalizedStrings({ localizeKeys: LOCALIZE_KEYS, @@ -166,7 +166,7 @@ test('Error returned with localizeKeys and incorrect language code', async () => }); test('Default language is english when no language provided with localizeKey', async () => { - const LOCALIZE_KEY = '%submitButton%'; + const LOCALIZE_KEY = '%general_button_submit%'; const response = await localizationDataProviderEngine.getLocalizedString({ localizeKey: LOCALIZE_KEY, }); @@ -174,18 +174,18 @@ test('Default language is english when no language provided with localizeKey', a }); test('Default language is english when no language provided with localizeKeys', async () => { - const LOCALIZE_KEYS: LocalizeKey[] = ['%submitButton%', '%some_localization_key%']; + const LOCALIZE_KEYS: LocalizeKey[] = ['%general_button_submit%', '%some_localization_key%']; const response = await localizationDataProviderEngine.getLocalizedStrings({ localizeKeys: LOCALIZE_KEYS, }); expect(response).toEqual({ '%some_localization_key%': 'This is the English text for %some_localization_key%.', - '%submitButton%': 'Submit', + '%general_button_submit%': 'Submit', }); }); test('Good key and missing but valid language code return default English', async () => { - const LOCALIZE_KEY = '%submitButton%'; + const LOCALIZE_KEY = '%general_button_submit%'; globalThis.isPackaged = true; const response = await localizationDataProviderEngine.getLocalizedString({ localizeKey: LOCALIZE_KEY, @@ -195,13 +195,13 @@ test('Good key and missing but valid language code return default English', asyn }); test('Good keys and missing but valid language code return default English', async () => { - const LOCALIZE_KEYS: LocalizeKey[] = ['%submitButton%', '%some_localization_key%']; + const LOCALIZE_KEYS: LocalizeKey[] = ['%general_button_submit%', '%some_localization_key%']; const response = await localizationDataProviderEngine.getLocalizedStrings({ localizeKeys: LOCALIZE_KEYS, locales: ['qya-Latn'], }); expect(response).toEqual({ '%some_localization_key%': 'This is the English text for %some_localization_key%.', - '%submitButton%': 'Submit', + '%general_button_submit%': 'Submit', }); }); diff --git a/src/extension-host/services/menu-data.service-host.test.tsx b/src/extension-host/services/menu-data.service-host.test.tsx index 8cd8e69c06..bae42e6ffb 100644 --- a/src/extension-host/services/menu-data.service-host.test.tsx +++ b/src/extension-host/services/menu-data.service-host.test.tsx @@ -6,9 +6,9 @@ const MOCK_MENU_DATA: PlatformMenus = { mainMenu: { columns: { 'paratext.paratext': { label: '%mainMenu_Paratext%', order: 0 }, - 'platform.window': { label: '%mainMenu_Window%', order: 1 }, - 'platform.layout': { label: '%mainMenu_Layout%', order: 2 }, - 'platform.help': { label: '%mainMenu_Help%', order: 3, isExtensible: true }, + 'platform.window': { label: '%mainMenu_window%', order: 1 }, + 'platform.layout': { label: '%mainMenu_layout%', order: 2 }, + 'platform.help': { label: '%mainMenu_help%', order: 3, isExtensible: true }, isExtensible: true, }, groups: { @@ -95,8 +95,8 @@ const MOCK_MENU_DATA: PlatformMenus = { }, defaultWebViewTopMenu: { columns: { - 'platform.project': { label: '%webView_Project%', order: 1 }, - 'platform.edit': { label: '%webView_Edit%', order: 2, isExtensible: true }, + 'platform.project': { label: '%webView_project%', order: 1 }, + 'platform.edit': { label: '%webView_edit%', order: 2, isExtensible: true }, }, groups: { 'platform.projectTop': { column: 'platform.project', order: 1 }, @@ -137,7 +137,7 @@ const MOCK_MENU_DATA: PlatformMenus = { command: 'platform.insertNote', }, { - label: '%wordList%', + label: '%menuItemName_wordList%', localizeNotes: 'Web view context menu > Word list...', group: 'platform.wordList', order: 1, diff --git a/src/renderer/components/dialogs/select-books-dialog.component.tsx b/src/renderer/components/dialogs/select-books-dialog.component.tsx index 67c3c8a05a..913f8b7975 100644 --- a/src/renderer/components/dialogs/select-books-dialog.component.tsx +++ b/src/renderer/components/dialogs/select-books-dialog.component.tsx @@ -2,6 +2,7 @@ import { Check } from 'lucide-react'; import { Button, Checklist, Label } from 'platform-bible-react'; import { useState } from 'react'; import { Canon } from '@sillsdev/scripture'; +import { LocalizeKey } from 'platform-bible-utils'; import DIALOG_BASE from './dialog-base.data'; import { DialogDefinition, DialogTypes, SELECT_BOOKS_DIALOG_TYPE } from './dialog-definition.model'; import './select-books-dialog.component.scss'; @@ -47,10 +48,12 @@ function SelectBooksDialog({ ); } +const localizeSelectBooksKey: LocalizeKey = `%selectBooks_title_selectBooks%`; + const SELECT_BOOKS_DIALOG: DialogDefinition = Object.freeze({ ...DIALOG_BASE, tabType: SELECT_BOOKS_DIALOG_TYPE, - defaultTitle: 'Select Books', + defaultTitle: localizeSelectBooksKey, initialSize: { width: 500, height: 400, diff --git a/src/renderer/components/dialogs/select-multiple-projects.dialog.tsx b/src/renderer/components/dialogs/select-multiple-projects.dialog.tsx index 6f495c107a..6095f28b5d 100644 --- a/src/renderer/components/dialogs/select-multiple-projects.dialog.tsx +++ b/src/renderer/components/dialogs/select-multiple-projects.dialog.tsx @@ -7,7 +7,7 @@ import ProjectList, { } from '@renderer/components/projects/project-list.component'; import '@renderer/components/dialogs/select-multiple-projects.dialog.scss'; import projectLookupService from '@shared/services/project-lookup.service'; -import { Button, usePromise } from 'platform-bible-react'; +import { Button, LocalizeKey, usePromise } from 'platform-bible-react'; import DIALOG_BASE from '@renderer/components/dialogs/dialog-base.data'; import { DialogDefinition, @@ -103,12 +103,14 @@ function SelectMultipleProjectsDialog({ ); } +const localizeSelectProjectsKey: LocalizeKey = '%selectMultipleProjects_title_selectProjects%'; + const SELECT_MULTIPLE_PROJECTS_DIALOG: DialogDefinition< typeof SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE > = Object.freeze({ ...DIALOG_BASE, tabType: SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE, - defaultTitle: 'Select Projects', + defaultTitle: localizeSelectProjectsKey, initialSize: { width: 500, height: 350, diff --git a/src/renderer/components/dialogs/select-project.dialog.tsx b/src/renderer/components/dialogs/select-project.dialog.tsx index 4023db80ce..c50fe53703 100644 --- a/src/renderer/components/dialogs/select-project.dialog.tsx +++ b/src/renderer/components/dialogs/select-project.dialog.tsx @@ -15,6 +15,7 @@ import { } from '@renderer/components/dialogs/dialog-definition.model'; import { papiFrontendProjectDataProviderService } from '@shared/services/project-data-provider.service'; import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; +import { LocalizeKey } from 'platform-bible-utils'; function SelectProjectDialog({ prompt, @@ -78,10 +79,12 @@ function SelectProjectDialog({ ); } +const localizeSelectProject: LocalizeKey = '%selectProject_title%'; + const SELECT_PROJECT_DIALOG: DialogDefinition = Object.freeze({ ...DIALOG_BASE, tabType: SELECT_PROJECT_DIALOG_TYPE, - defaultTitle: 'Select Project', + defaultTitle: localizeSelectProject, initialSize: { width: 500, height: 350, diff --git a/src/renderer/components/docking/error-tab.component.tsx b/src/renderer/components/docking/error-tab.component.tsx index 39f59fb1d7..3b62001179 100644 --- a/src/renderer/components/docking/error-tab.component.tsx +++ b/src/renderer/components/docking/error-tab.component.tsx @@ -1,5 +1,5 @@ import { SavedTabInfo, TabInfo } from '@shared/models/docking-framework.model'; -import { newGuid } from 'platform-bible-utils'; +import { LocalizeKey, newGuid } from 'platform-bible-utils'; export type ErrorTabData = { errorMessage: string }; @@ -16,12 +16,14 @@ export default function ErrorTab({ errorMessage }: { errorMessage: string }) { ); } +const localizeError: LocalizeKey = '%tab_title_error%'; + /** Creates a new error message tab with the specified error message */ export const createErrorTab = (errorMessage: string): TabInfo => { return { id: newGuid(), tabType: 'error', - tabTitle: 'Error', + tabTitle: localizeError, content: , minWidth: 150, minHeight: 150, diff --git a/src/renderer/components/docking/platform-tab-title.component.tsx b/src/renderer/components/docking/platform-tab-title.component.tsx index 07a23ba513..f2b1d26b64 100644 --- a/src/renderer/components/docking/platform-tab-title.component.tsx +++ b/src/renderer/components/docking/platform-tab-title.component.tsx @@ -2,8 +2,9 @@ import { Tooltip } from '@mui/material'; import { CommandHandler, HamburgerMenuButton } from 'platform-bible-react'; import './platform-tab-title.component.scss'; import menuDataService from '@shared/services/menu-data.service'; -import { useData } from '@renderer/hooks/papi-hooks'; -import { useCallback, useRef } from 'react'; +import { useData, useLocalizedStrings } from '@renderer/hooks/papi-hooks'; +import { useCallback, useMemo, useRef } from 'react'; +import { isLocalizeKey, LocalizeKey } from 'platform-bible-utils'; import { handleMenuCommand } from '../platform-bible-menu.commands'; type PlatformTabTitleProps = { @@ -14,7 +15,7 @@ type PlatformTabTitleProps = { /** Url to image to show on the tab. Defaults to the software's standard logo. */ iconUrl?: string; /** Text to show on the tab */ - text: string; + text: string | LocalizeKey; /** Text to show when hovering over the tab. Defaults to empty string */ tooltip?: string; }; @@ -35,6 +36,13 @@ export default function PlatformTabTitle({ }: PlatformTabTitleProps) { const menuSelector = webViewType ?? 'invalid.invalid'; + const tabAria: LocalizeKey = '%tab_aria_tab%'; + const [localizedStrings] = useLocalizedStrings( + useMemo(() => (isLocalizeKey(text) ? [text, tabAria] : [tabAria]), [text]), + ); + const title = isLocalizeKey(text) ? localizedStrings[text] : text; + const tabLabel = localizedStrings[tabAria]; + const menuInfo = useData(webViewType ? menuDataService.dataProviderName : undefined).WebViewMenu( menuSelector, { @@ -82,13 +90,13 @@ export default function PlatformTabTitle({ commandHandler={commandHandler} normalMenu={webViewMenu?.topMenu} className="tab-menu-button" - aria-label="Tab" + aria-label={tabLabel} containerRef={containerRef} > {icon} )} - {text} + {title} ); diff --git a/src/renderer/components/projects/download-update-project-tab.component.tsx b/src/renderer/components/projects/download-update-project-tab.component.tsx index eed61f434b..fe31df022a 100644 --- a/src/renderer/components/projects/download-update-project-tab.component.tsx +++ b/src/renderer/components/projects/download-update-project-tab.component.tsx @@ -17,6 +17,7 @@ import ProjectList, { Project, } from '@renderer/components/projects/project-list.component'; import './download-update-project-tab.component.scss'; +import { useLocalizedStrings } from '@renderer/hooks/papi-hooks'; export const TAB_TYPE_DOWNLOAD_UPDATE_PROJECT_DIALOG = 'download-update-project-dialog'; @@ -33,6 +34,29 @@ function deleteProject(project: Project) { } export default function DownloadUpdateProjectTab() { + const downloadableProjectsAriaKey = '%downloadUpdateProjectTab_aria_downloadable%'; + const downloadableProjectsHeaderKey = '%downloadUpdateProjectTab_listHeader_downloadable%'; + const downloadedProjectsAriaKey = '%downloadUpdateProjectTab_aria_downloaded%'; + const downloadedProjectsHeaderKey = '%downloadUpdateProjectTab_listHeader_downloaded%'; + const deleteListItemKey = '%downloadUpdateProjectTab_button_delete%'; + const [localizedStrings] = useLocalizedStrings( + useMemo( + () => [ + downloadableProjectsAriaKey, + downloadableProjectsHeaderKey, + downloadedProjectsAriaKey, + downloadedProjectsHeaderKey, + deleteListItemKey, + ], + [], + ), + ); + const localizedDownloadableProjectsAria = localizedStrings[downloadableProjectsAriaKey]; + const localizedDownloadableProjectsHeader = localizedStrings[downloadableProjectsHeaderKey]; + const localizedDownloadedProjectsAria = localizedStrings[downloadedProjectsAriaKey]; + const localizedDownloadedProjectsHeader = localizedStrings[downloadedProjectsHeaderKey]; + const localizedDeleteListItem = localizedStrings[deleteListItemKey]; + const [downloadableProjects, downloadedProjects] = useMemo(() => { const projects = fetchProjects(); return [ @@ -43,10 +67,10 @@ export default function DownloadUpdateProjectTab() { return (
-
@@ -113,7 +122,7 @@ export const loadProjectSettingsTab = (savedTabInfo: SavedTabInfo): TabInfo => { return { ...savedTabInfo, - tabTitle: 'Project Settings', + tabTitle: '%settings_title_projectSettings%', content: , }; }; @@ -121,7 +130,7 @@ export const loadProjectSettingsTab = (savedTabInfo: SavedTabInfo): TabInfo => { export const loadUserSettingsTab = (savedTabInfo: SavedTabInfo): TabInfo => { return { ...savedTabInfo, - tabTitle: 'User Settings', + tabTitle: '%settings_title_userSettings%', content: , }; }; diff --git a/src/renderer/components/web-view.component.test.ts b/src/renderer/components/web-view.component.test.ts deleted file mode 100644 index 4c15371d2f..0000000000 --- a/src/renderer/components/web-view.component.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { WebViewContentType } from '@shared/models/web-view.model'; -import { WebViewTabProps } from '@shared/models/docking-framework.model'; -import { getTitle } from './web-view.component'; - -describe('WebView Component', () => { - describe('getTitle', () => { - it('can get title when title is defined', () => { - const title = 'MyTitle'; - const props: WebViewTabProps = { - id: '', - webViewType: '', - content: '', - contentType: WebViewContentType.HTML, - title, - }; - expect(getTitle(props)).toEqual(title); - }); - - it('can get title when title is undefined but webViewType exists', () => { - const webViewType = 'my-webview'; - const title = `${webViewType} Web View`; - const props: WebViewTabProps = { - id: '', - webViewType, - content: '', - contentType: WebViewContentType.HTML, - }; - expect(getTitle(props)).toEqual(title); - }); - - it('can get title when title is undefined and webViewType is empty but contentType exists', () => { - const contentType = WebViewContentType.HTML; - const title = `${contentType} Web View`; - const props: WebViewTabProps = { - id: '', - webViewType: '', - content: '', - contentType, - }; - expect(getTitle(props)).toEqual(title); - }); - }); -}); diff --git a/src/renderer/components/web-view.component.tsx b/src/renderer/components/web-view.component.tsx index b4cb80922b..edcabb7be6 100644 --- a/src/renderer/components/web-view.component.tsx +++ b/src/renderer/components/web-view.component.tsx @@ -13,19 +13,20 @@ import { updateWebViewDefinitionSync, } from '@renderer/services/web-view.service-host'; import logger from '@shared/services/logger.service'; -import { getLocalizeKeysForScrollGroupIds, serialize } from 'platform-bible-utils'; import { BookChapterControl, ScrollGroupSelector, useEvent } from 'platform-bible-react'; import './web-view.component.css'; import { useLocalizedStrings, useScrollGroupScrRef } from '@renderer/hooks/papi-hooks'; import { availableScrollGroupIds } from '@renderer/services/scroll-group.service-host'; import { getNetworkEvent } from '@shared/services/network.service'; +import { + formatReplacementString, + isLocalizeKey, + serialize, + getLocalizeKeysForScrollGroupIds, +} from 'platform-bible-utils'; export const TAB_TYPE_WEBVIEW = 'webView'; -export function getTitle({ webViewType, title, contentType }: Partial): string { - return title || `${webViewType || contentType} Web View`; -} - const scrollGroupLocalizedStringKeys = getLocalizeKeysForScrollGroupIds(availableScrollGroupIds); /** @@ -71,6 +72,22 @@ export default function WebView({ }, [webViewType, id]), ); + const webViewKey = '%webView_defaultTitle_webView%'; + const webViewTitleTypeFormatStr = '%webView_title_type_formatString%'; + const [localizedStrings] = useLocalizedStrings( + title && isLocalizeKey(title) + ? [title, webViewTitleTypeFormatStr] + : [webViewKey, webViewTitleTypeFormatStr], + ); + const localizedWebViewTitleFormatStr = localizedStrings[webViewTitleTypeFormatStr]; + const defaultTitle = + title ?? + formatReplacementString(localizedWebViewTitleFormatStr, { + type: webViewType || contentType, + defaultTitle: localizedStrings[webViewKey], + }); + const localizedTitle = title && isLocalizeKey(title) ? localizedStrings[title] : defaultTitle; + /** Whether this webview's iframe will be populated by `src` as opposed to `srcdoc` */ const shouldUseSrc = contentType === WebViewContentType.URL; @@ -105,7 +122,7 @@ export default function WebView({