From ef0a55ced13d62c65656abbb6176e81d0ceddda9 Mon Sep 17 00:00:00 2001 From: Cyprien P Date: Tue, 31 Jan 2023 17:35:46 +0100 Subject: [PATCH] (core) Makes GristTooltips translatable Test Plan: Should no break anything Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3785 --- .../components/BehavioralPromptsManager.ts | 2 +- app/client/ui/GristTooltips.ts | 124 +++++++++--------- app/client/ui/HomeIntro.ts | 2 +- static/locales/en.client.json | 41 +++++- 4 files changed, 107 insertions(+), 62 deletions(-) diff --git a/app/client/components/BehavioralPromptsManager.ts b/app/client/components/BehavioralPromptsManager.ts index b533b908..5a83594f 100644 --- a/app/client/components/BehavioralPromptsManager.ts +++ b/app/client/components/BehavioralPromptsManager.ts @@ -90,7 +90,7 @@ export class BehavioralPromptsManager extends Disposable { const {hideArrow = false, onDispose, popupOptions} = options; const {title, content} = GristBehavioralPrompts[prompt]; - const ctl = showBehavioralPrompt(refElement, title, content(), { + const ctl = showBehavioralPrompt(refElement, title(), content(), { onClose: (dontShowTips) => { if (dontShowTips) { this._dontShowTips(); } this._markAsSeen(prompt); diff --git a/app/client/ui/GristTooltips.ts b/app/client/ui/GristTooltips.ts index 23494878..f92d4a1c 100644 --- a/app/client/ui/GristTooltips.ts +++ b/app/client/ui/GristTooltips.ts @@ -2,7 +2,10 @@ import {cssLink} from 'app/client/ui2018/links'; import {commonUrls} from 'app/common/gristUrls'; import {BehavioralPrompt} from 'app/common/Prefs'; import {dom, DomContents, DomElementArg, styled} from 'grainjs'; -import { icon } from '../ui2018/icons'; +import {icon} from 'app/client/ui2018/icons'; +import {makeT} from 'app/client/lib/localization'; + +const t = makeT('GristTooltips'); const cssTooltipContent = styled('div', ` display: flex; @@ -34,36 +37,35 @@ export type Tooltip = export type TooltipContentFunc = (...domArgs: DomElementArg[]) => DomContents; -// TODO: i18n export const GristTooltips: Record = { dataSize: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'The total size of all data in this document, excluding attachments.'), - dom('div', 'Updates every 5 minutes.'), + dom('div', t('The total size of all data in this document, excluding attachments.')), + dom('div', t('Updates every 5 minutes.')), ...args, ), setTriggerFormula: (...args: DomElementArg[]) => cssTooltipContent( dom('div', - 'Formulas that trigger in certain cases, and store the calculated value as data.' + t('Formulas that trigger in certain cases, and store the calculated value as data.') ), dom('div', - 'Useful for storing the timestamp or author of a new record, data cleaning, and ' - + 'more.' + t('Useful for storing the timestamp or author of a new record, data cleaning, and ' + + 'more.') ), dom('div', - cssLink({href: commonUrls.helpTriggerFormulas, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpTriggerFormulas, target: '_blank'}, t('Learn more.')), ), ...args, ), selectBy: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Link your new widget to an existing widget on this page.'), + dom('div', t('Link your new widget to an existing widget on this page.')), dom('div', - cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, t('Learn more.')), ), ...args, ), workOnACopy: (...args: DomElementArg[]) => cssTooltipContent( dom('div', - 'Try out changes in a copy, then decide whether to replace the original with your edits.' + t('Try out changes in a copy, then decide whether to replace the original with your edits.') ), dom('div', cssLink({href: commonUrls.helpTryingOutChanges, target: '_blank'}, 'Learn more.'), @@ -72,127 +74,131 @@ export const GristTooltips: Record = { ), openAccessRules: (...args: DomElementArg[]) => cssTooltipContent( dom('div', - 'Access rules give you the power to create nuanced rules to determine who can ' - + 'see or edit which parts of your document.' + t('Access rules give you the power to create nuanced rules to determine who can ' + + 'see or edit which parts of your document.') ), dom('div', - cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.')), ), ...args, ), addRowConditionalStyle: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Apply conditional formatting to rows based on formulas.'), + dom('div', t('Apply conditional formatting to rows based on formulas.')), dom('div', - cssLink({href: commonUrls.helpConditionalFormatting, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpConditionalFormatting, target: '_blank'}, t('Learn more.')), ), ...args, ), addColumnConditionalStyle: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Apply conditional formatting to cells in this column when formula conditions are met.'), - dom('div', 'Click on “Open row styles” to apply conditional formatting to rows.'), + dom('div', t('Apply conditional formatting to cells in this column when formula conditions are met.')), + dom('div', t('Click on “Open row styles” to apply conditional formatting to rows.')), dom('div', - cssLink({href: commonUrls.helpConditionalFormatting, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpConditionalFormatting, target: '_blank'}, t('Learn more.')), ), ...args, ), }; export interface BehavioralPromptContent { - title: string; + title: () => string; content: (...domArgs: DomElementArg[]) => DomContents; } -// TODO: i18n export const GristBehavioralPrompts: Record = { referenceColumns: { - title: 'Reference Columns', + title: () => t('Reference Columns'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Reference columns are the key to ', cssBoldText('relational'), ' data in Grist.'), - dom('div', 'They allow for one record to point (or refer) to another.'), + dom('div', t('Reference columns are the key to {{relational}} data in Grist.', { + relational: cssBoldText(t('relational')) + })), + dom('div', t('They allow for one record to point (or refer) to another.')), dom('div', - cssLink({href: commonUrls.helpColRefs, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpColRefs, target: '_blank'}, t('Learn more.')), ), ...args, ), }, referenceColumnsConfig: { - title: 'Reference Columns', + title: () => t('Reference Columns'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Select the table to link to.'), - dom('div', 'Cells in a reference column always identify an ', cssItalicizedText('entire'), - ' record in that table, but you may select which column from that record to show.'), + dom('div', t('Select the table to link to.')), + dom('div', t('Cells in a reference column always identify an {{entire}} ' + + 'record in that table, but you may select which column from that record to show.', { + entire: cssItalicizedText(t('entire')) + })), dom('div', - cssLink({href: commonUrls.helpUnderstandingReferenceColumns, target: '_blank'}, 'Learn more.'), + cssLink({href: commonUrls.helpUnderstandingReferenceColumns, target: '_blank'}, t('Learn more.')), ), ...args, ), }, rawDataPage: { - title: 'Raw Data page', + title: () => t('Raw Data page'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'The Raw Data page lists all data tables in your document, ' - + 'including summary tables and tables not included in page layouts.'), - dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, 'Learn more.')), + dom('div', t('The Raw Data page lists all data tables in your document, ' + + 'including summary tables and tables not included in page layouts.')), + dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, t('Learn more.'))), ...args, ), }, accessRules: { - title: 'Access Rules', + title: () => t('Access Rules'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Access rules give you the power to create nuanced rules ' - + 'to determine who can see or edit which parts of your document.'), - dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, 'Learn more.')), + dom('div', t('Access rules give you the power to create nuanced rules ' + + 'to determine who can see or edit which parts of your document.')), + dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.'))), ...args, ), }, filterButtons: { - title: 'Pinning Filters', + title: () => t('Pinning Filters'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Pinned filters are displayed as buttons above the widget.'), - dom('div', 'Unpin to hide the the button while keeping the filter.'), - dom('div', cssLink({href: commonUrls.helpFilterButtons, target: '_blank'}, 'Learn more.')), + dom('div', t('Pinned filters are displayed as buttons above the widget.')), + dom('div', t('Unpin to hide the the button while keeping the filter.')), + dom('div', cssLink({href: commonUrls.helpFilterButtons, target: '_blank'}, t('Learn more.'))), ...args, ), }, nestedFiltering: { - title: 'Nested Filtering', + title: () => t('Nested Filtering'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'You can filter by more than one column.'), - dom('div', 'Only those rows will appear which match all of the filters.'), + dom('div', t('You can filter by more than one column.')), + dom('div', t('Only those rows will appear which match all of the filters.')), ...args, ), }, pageWidgetPicker: { - title: 'Selecting Data', + title: () => t('Selecting Data'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Select the table containing the data to show.'), - dom('div', 'Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.'), + dom('div', t('Select the table containing the data to show.')), + dom('div', t('Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.')), ...args, ), }, pageWidgetPickerSelectBy: { - title: 'Linking Widgets', + title: () => t('Linking Widgets'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Link your new widget to an existing widget on this page.'), - dom('div', `This is the secret to Grist's dynamic and productive layouts.`), - dom('div', cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, 'Learn more.')), + dom('div', t('Link your new widget to an existing widget on this page.')), + dom('div', t('This is the secret to Grist\'s dynamic and productive layouts.')), + dom('div', cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, t('Learn more.'))), ...args, ), }, editCardLayout: { - title: 'Editing Card Layout', + title: () => t('Editing Card Layout'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Rearrange the fields in your card by dragging and resizing cells.'), - dom('div', 'Clicking ', cssIcon('EyeHide'), - ' in each cell hides the field from this view without deleting it.'), + dom('div', t('Rearrange the fields in your card by dragging and resizing cells.')), + dom('div', t('Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.', { + EyeHideIcon: cssIcon('EyeHide') + })), ...args, ), }, addNew: { - title: 'Add New', + title: () => t('Add New'), content: (...args: DomElementArg[]) => cssTooltipContent( - dom('div', 'Click the Add New button to create new documents or workspaces, ' - + 'or import data.'), + dom('div', t('Click the Add New button to create new documents or workspaces, ' + + 'or import data.')), ...args, ), }, diff --git a/app/client/ui/HomeIntro.ts b/app/client/ui/HomeIntro.ts index b775d473..8f1dd578 100644 --- a/app/client/ui/HomeIntro.ts +++ b/app/client/ui/HomeIntro.ts @@ -117,7 +117,7 @@ function makeAnonIntro(homeModel: HomeModel) { return [ css.docListHeader(t("Welcome to Grist!"), testId('welcome-title')), cssIntroLine(t("Get started by exploring templates, or creating your first Grist document.")), - cssIntroLine(signUp, ' to save your work. ', // TODO i18n + cssIntroLine(t("{{signUp}} to save your work. ", {signUp}), (shouldHideUiElement('helpCenter') ? null : t("Visit our {{link}} to learn more.", { link: helpCenterLink() })), testId('welcome-text')), makeCreateButtons(homeModel), diff --git a/static/locales/en.client.json b/static/locales/en.client.json index 7f3db564..f5a2752a 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -402,7 +402,8 @@ "Welcome to Grist, {{name}}!": "Welcome to Grist, {{name}}!", "Welcome to {{orgName}}": "Welcome to {{orgName}}", "You have read-only access to this site. Currently there are no documents.": "You have read-only access to this site. Currently there are no documents.", - "personal site": "personal site" + "personal site": "personal site", + "{{signUp}} to save your work. ": "{{signUp}} to save your work. " }, "HomeLeftPane": { "Access Details": "Access Details", @@ -899,5 +900,43 @@ }, "LanguageMenu": { "Language": "Language" + }, + "GristTooltips": { + "Apply conditional formatting to cells in this column when formula conditions are met.": "Apply conditional formatting to cells in this column when formula conditions are met.", + "Apply conditional formatting to rows based on formulas.": "Apply conditional formatting to rows based on formulas.", + "Cells in a reference column always identify an {{entire}} record in that table, but you may select which column from that record to show.": "Cells in a reference column always identify an {{entire}} record in that table, but you may select which column from that record to show.", + "Click on “Open row styles” to apply conditional formatting to rows.": "Click on “Open row styles” to apply conditional formatting to rows.", + "Click the Add New button to create new documents or workspaces, or import data.": "Click the Add New button to create new documents or workspaces, or import data.", + "Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.": "Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.", + "Editing Card Layout": "Editing Card Layout", + "Formulas that trigger in certain cases, and store the calculated value as data.": "Formulas that trigger in certain cases, and store the calculated value as data.", + "Learn more.": "Learn more.", + "Link your new widget to an existing widget on this page.": "Link your new widget to an existing widget on this page.", + "Linking Widgets": "Linking Widgets", + "Nested Filtering": "Nested Filtering", + "Only those rows will appear which match all of the filters.": "Only those rows will appear which match all of the filters.", + "Pinned filters are displayed as buttons above the widget.": "Pinned filters are displayed as buttons above the widget.", + "Pinning Filters": "Pinning Filters", + "Raw Data page": "Raw Data page", + "Rearrange the fields in your card by dragging and resizing cells.": "Rearrange the fields in your card by dragging and resizing cells.", + "Reference Columns": "Reference Columns", + "Reference columns are the key to {{relational}} data in Grist.": "Reference columns are the key to {{relational}} data in Grist.", + "Select the table containing the data to show.": "Select the table containing the data to show.", + "Select the table to link to.": "Select the table to link to.", + "Selecting Data": "Selecting Data", + "The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.", + "The total size of all data in this document, excluding attachments.": "The total size of all data in this document, excluding attachments.", + "They allow for one record to point (or refer) to another.": "They allow for one record to point (or refer) to another.", + "This is the secret to Grist's dynamic and productive layouts.": "This is the secret to Grist's dynamic and productive layouts.", + "Try out changes in a copy, then decide whether to replace the original with your edits.": "Try out changes in a copy, then decide whether to replace the original with your edits.", + "Unpin to hide the the button while keeping the filter.": "Unpin to hide the the button while keeping the filter.", + "Updates every 5 minutes.": "Updates every 5 minutes.", + "Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.", + "Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Useful for storing the timestamp or author of a new record, data cleaning, and more.", + "You can filter by more than one column.": "You can filter by more than one column.", + "entire": "entire", + "relational": "relational", + "Access Rules": "Access Rules", + "Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document." } }