diff --git a/app/client/ui/AccountPage.ts b/app/client/ui/AccountPage.ts index 57e5c4f2..383fd21f 100644 --- a/app/client/ui/AccountPage.ts +++ b/app/client/ui/AccountPage.ts @@ -58,13 +58,13 @@ export class AccountPage extends Disposable { const {enableCustomCss} = getGristConfig(); return domComputed(this._userObs, (user) => user && ( css.container(css.accountPage( - css.header(t('AccountSettings')), + css.header(t("Account settings")), css.dataRow( - css.inlineSubHeader(t('Email')), + css.inlineSubHeader(t("Email")), css.email(user.email), ), css.dataRow( - css.inlineSubHeader(t('Name')), + css.inlineSubHeader(t("Name")), domComputed(this._isEditingName, (isEditing) => ( isEditing ? [ transientInput( @@ -78,13 +78,13 @@ export class AccountPage extends Disposable { css.flexGrow.cls(''), ), css.textBtn( - css.icon('Settings'), t('Save'), + css.icon('Settings'), t("Save"), // No need to save on 'click'. The transient input already does it on close. ), ] : [ css.name(user.name), css.textBtn( - css.icon('Settings'), t('Edit'), + css.icon('Settings'), t("Edit"), dom.on('click', () => this._isEditingName.set(true)), ), ] @@ -93,11 +93,11 @@ export class AccountPage extends Disposable { ), // show warning for invalid name but not for the empty string dom.maybe(use => use(this._nameEdit) && !use(this._isNameValid), cssWarnings), - css.header(t('PasswordSecurity')), + css.header(t("Password & Security")), css.dataRow( - css.inlineSubHeader(t("LoginMethod")), + css.inlineSubHeader(t("Login Method")), css.loginMethod(user.loginMethod), - user.loginMethod === 'Email + Password' ? css.textBtn(t("ChangePassword"), + user.loginMethod === 'Email + Password' ? css.textBtn(t("Change Password"), dom.on('click', () => this._showChangePasswordDialog()), ) : null, testId('login-method'), @@ -106,24 +106,24 @@ export class AccountPage extends Disposable { css.dataRow( labeledSquareCheckbox( this._allowGoogleLogin, - t('AllowGoogleSigning'), + t("Allow signing in to this account with Google"), testId('allow-google-login-checkbox'), ), testId('allow-google-login'), ), - css.subHeader(t('TwoFactorAuth')), + css.subHeader(t("Two-factor authentication")), css.description( - t("TwoFactorAuthDescription") + t("Two-factor authentication is an extra layer of security for your Grist account designed to ensure that you're the only person who can access your account, even if someone knows your password.") ), dom.create(MFAConfig, user), ), // Custom CSS is incompatible with custom themes. enableCustomCss ? null : [ - css.header(t('Theme')), + css.header(t("Theme")), dom.create(ThemeConfig, this._appModel), ], - css.header(t('API')), - css.dataRow(css.inlineSubHeader(t('APIKey')), css.content( + css.header(t("API")), + css.dataRow(css.inlineSubHeader(t("API Key")), css.content( dom.create(ApiKey, { apiKey: this._apiKey, onCreate: () => this._createApiKey(), @@ -214,7 +214,7 @@ export function checkName(name: string): boolean { */ function buildNameWarningsDom() { return css.warning( - t("WarningUsername"), + t("Names only allow letters, numbers and certain special characters"), testId('username-warning'), ); } diff --git a/app/client/ui/AccountWidget.ts b/app/client/ui/AccountWidget.ts index 272a1a11..4940b208 100644 --- a/app/client/ui/AccountWidget.ts +++ b/app/client/ui/AccountWidget.ts @@ -36,7 +36,7 @@ export class AccountWidget extends Disposable { cssUserIcon(createUserImage(user, 'medium', testId('user-icon')), menu(() => this._makeAccountMenu(user), {placement: 'bottom-end'}), ) : - cssSignInButton(t('SignIn'), icon('Collapse'), testId('user-signin'), + cssSignInButton(t("Sign in"), icon('Collapse'), testId('user-signin'), menu(() => this._makeAccountMenu(user), {placement: 'bottom-end'}), ) ) @@ -57,24 +57,24 @@ export class AccountWidget extends Disposable { // The 'Document Settings' item, when there is an open document. const documentSettingsItem = (gristDoc ? menuItem(async () => (await loadGristDoc()).showDocSettingsModal(gristDoc.docInfo, this._docPageModel!), - t('DocumentSettings'), + t("Document Settings"), testId('dm-doc-settings')) : null); // The item to toggle mobile mode (presence of viewport meta tag). const mobileModeToggle = menuItem(viewport.toggleViewport, cssSmallDeviceOnly.cls(''), // Only show this toggle on small devices. - t('ToggleMobileMode'), + t("Toggle Mobile Mode"), cssCheckmark('Tick', dom.show(viewport.viewportEnabled)), testId('usermenu-toggle-mobile'), ); if (!user) { return [ - menuItemLink({href: getLoginOrSignupUrl()}, t('SignIn')), + menuItemLink({href: getLoginOrSignupUrl()}, t("Sign in")), menuDivider(), documentSettingsItem, - menuItemLink({href: commonUrls.plans}, t('Pricing')), + menuItemLink({href: commonUrls.plans}, t("Pricing")), mobileModeToggle, ]; } @@ -88,14 +88,14 @@ export class AccountWidget extends Disposable { cssEmail(user.email, testId('usermenu-email')) ) ), - menuItemLink(urlState().setLinkUrl({account: 'account'}), t('ProfileSettings')), + menuItemLink(urlState().setLinkUrl({account: 'account'}), t("Profile Settings")), documentSettingsItem, // Show 'Organization Settings' when on a home page of a valid org. (!this._docPageModel && currentOrg && this._appModel.isTeamSite ? menuItem(() => manageTeamUsers(currentOrg, user, this._appModel.api), - roles.canEditAccess(currentOrg.access) ? t('ManageTeam') : t('AccessDetails'), + roles.canEditAccess(currentOrg.access) ? t("Manage Team") : t("Access Details"), testId('dm-org-access')) : // Don't show on doc pages, or for personal orgs. null), @@ -111,7 +111,7 @@ export class AccountWidget extends Disposable { // org-listing UI below. this._appModel.topAppModel.isSingleOrg || shouldHideUiElement("multiAccounts") ? [] : [ menuDivider(), - menuSubHeader(dom.text((use) => use(users).length > 1 ? t('SwitchAccounts') : t('Accounts'))), + menuSubHeader(dom.text((use) => use(users).length > 1 ? t("Switch Accounts") : t("Accounts"))), dom.forEach(users, (_user) => { if (_user.id === user.id) { return null; } return menuItem(() => this._switchAccount(_user), @@ -119,10 +119,10 @@ export class AccountWidget extends Disposable { cssOtherEmail(_user.email, testId('usermenu-other-email')), ); }), - isExternal ? null : menuItemLink({href: getLoginUrl()}, t("AddAccount"), testId('dm-add-account')), + isExternal ? null : menuItemLink({href: getLoginUrl()}, t("Add Account"), testId('dm-add-account')), ], - menuItemLink({href: getLogoutUrl()}, t("SignOut"), testId('dm-log-out')), + menuItemLink({href: getLogoutUrl()}, t("Sign Out"), testId('dm-log-out')), maybeAddSiteSwitcherSection(this._appModel), ]; diff --git a/app/client/ui/AddNewButton.ts b/app/client/ui/AddNewButton.ts index 7ddfead1..1cd45dc1 100644 --- a/app/client/ui/AddNewButton.ts +++ b/app/client/ui/AddNewButton.ts @@ -10,7 +10,7 @@ export function addNewButton(isOpen: Observable | boolean = true, ...ar cssAddNewButton.cls('-open', isOpen), // Setting spacing as flex items allows them to shrink faster when there isn't enough space. cssLeftMargin(), - cssAddText(t('AddNew')), + cssAddText(t("Add New")), dom('div', {style: 'flex: 1 1 16px'}), cssPlusButton(cssPlusIcon('Plus')), dom('div', {style: 'flex: 0 1 16px'}), diff --git a/app/client/ui/ApiKey.ts b/app/client/ui/ApiKey.ts index 0d811ee3..0c9bdec2 100644 --- a/app/client/ui/ApiKey.ts +++ b/app/client/ui/ApiKey.ts @@ -55,7 +55,7 @@ export class ApiKey extends Disposable { }, dom.attr('type', (use) => use(this._isHidden) ? 'password' : 'text'), testId('key'), - {title: t('ClickToShow')}, + {title: t("Click to show")}, dom.on('click', (_ev, el) => { this._isHidden.set(false); setTimeout(() => el.select(), 0); @@ -67,7 +67,7 @@ export class ApiKey extends Disposable { this._inputArgs ), cssTextBtn( - cssTextBtnIcon('Remove'), t('Remove'), + cssTextBtnIcon('Remove'), t("Remove"), dom.on('click', () => this._showRemoveKeyModal()), testId('delete'), dom.boolAttr('disabled', (use) => use(this._loading) || this._anonymous) @@ -76,9 +76,9 @@ export class ApiKey extends Disposable { description(this._getDescription(), testId('description')), )), dom.maybe((use) => !(use(this._apiKey) || this._anonymous), () => [ - basicButton(t('Create'), dom.on('click', () => this._onCreate()), testId('create'), + basicButton(t("Create"), dom.on('click', () => this._onCreate()), testId('create'), dom.boolAttr('disabled', this._loading)), - description(t('ByGenerating'), testId('description')), + description(t("By generating an API key, you will be able to make API calls for your own account."), testId('description')), ]), ); } @@ -110,9 +110,9 @@ export class ApiKey extends Disposable { private _showRemoveKeyModal(): void { confirmModal( - t('RemoveAPIKey'), t('Remove'), + t("Remove API Key"), t("Remove"), () => this._onDelete(), - t("AboutToDeleteAPIKey") + t("You're about to delete an API key. This will cause all future requests using this API key to be rejected. Do you still want to delete?") ); } } diff --git a/app/client/ui/App.ts b/app/client/ui/App.ts index 3b0af4a5..ba2647bc 100644 --- a/app/client/ui/App.ts +++ b/app/client/ui/App.ts @@ -93,8 +93,8 @@ export class App extends DisposableWithEvents { dom('table.g-help-table', dom('thead', dom('tr', - dom('th', t('Key')), - dom('th', t('Description')) + dom('th', t("Key")), + dom('th', t("Description")) ) ), dom.forEach(commandList.groups, (group: any) => { @@ -234,7 +234,7 @@ export class App extends DisposableWithEvents { if (message.match(/MemoryError|unmarshallable object/)) { if (err.message.length > 30) { // TLDR - err.message = t('MemoryError'); + err.message = t("Memory Error"); } this._mostRecentDocPageModel?.offerRecovery(err); } diff --git a/app/client/ui/AppHeader.ts b/app/client/ui/AppHeader.ts index 7c830303..6fe7c0ce 100644 --- a/app/client/ui/AppHeader.ts +++ b/app/client/ui/AppHeader.ts @@ -57,11 +57,11 @@ export class AppHeader extends Disposable { this._orgName && cssDropdownIcon('Dropdown'), menu(() => [ menuSubHeader( - this._appModel.isTeamSite ? t('TeamSite') : t('PersonalSite') - + (this._appModel.isLegacySite ? ` (${t('Legacy')})` : ''), + this._appModel.isTeamSite ? t("Team Site") : t("Personal Site") + + (this._appModel.isLegacySite ? ` (${t("Legacy")})` : ''), testId('orgmenu-title'), ), - menuItemLink(urlState().setLinkUrl({}), t('HomePage'), testId('orgmenu-home-page')), + menuItemLink(urlState().setLinkUrl({}), t("Home Page"), testId('orgmenu-home-page')), // Show 'Organization Settings' when on a home page of a valid org. (!this._docPageModel && currentOrg && !currentOrg.owner ? diff --git a/app/client/ui/CellContextMenu.ts b/app/client/ui/CellContextMenu.ts index 46754128..7194b926 100644 --- a/app/client/ui/CellContextMenu.ts +++ b/app/client/ui/CellContextMenu.ts @@ -27,7 +27,7 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC const numRows: number = rowOptions.numRows; const nameDeleteRows = t("DeleteRows", {count: numRows}); - const nameClearCells = (numRows > 1 || numCols > 1) ? t('ClearValues') : t('ClearCell'); + const nameClearCells = (numRows > 1 || numCols > 1) ? t("Clear values") : t("Clear cell"); const result: Array = []; @@ -43,9 +43,9 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC ...( (numCols > 1 || numRows > 1) ? [] : [ menuDivider(), - menuItemCmd(allCommands.copyLink, t('CopyAnchorLink')), + menuItemCmd(allCommands.copyLink, t("Copy anchor link")), menuDivider(), - menuItemCmd(allCommands.filterByThisCellValue, t("FilterByValue")), + menuItemCmd(allCommands.filterByThisCellValue, t("Filter by this value")), menuItemCmd(allCommands.openDiscussion, 'Comment', dom.cls('disabled', ( isReadonly || numRows === 0 || numCols === 0 )), dom.hide(use => !use(COMMENTS()))) //TODO: i18next @@ -60,19 +60,19 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC // When the view is sorted, any newly added records get shifts instantly at the top or // bottom. It could be very confusing for users who might expect the record to stay above or // below the active row. Thus in this case we show a single `insert row` command. - [menuItemCmd(allCommands.insertRecordAfter, t("InsertRow"), + [menuItemCmd(allCommands.insertRecordAfter, t("Insert row"), dom.cls('disabled', disableInsert))] : - [menuItemCmd(allCommands.insertRecordBefore, t("InsertRowAbove"), + [menuItemCmd(allCommands.insertRecordBefore, t("Insert row above"), dom.cls('disabled', disableInsert)), - menuItemCmd(allCommands.insertRecordAfter, t("InsertRowBelow"), + menuItemCmd(allCommands.insertRecordAfter, t("Insert row below"), dom.cls('disabled', disableInsert))] ), menuItemCmd(allCommands.duplicateRows, t("DuplicateRows", {count: numRows}), dom.cls('disabled', disableInsert || numRows === 0)), - menuItemCmd(allCommands.insertFieldBefore, t("InsertColumnLeft"), + menuItemCmd(allCommands.insertFieldBefore, t("Insert column to the left"), disableForReadonlyView), - menuItemCmd(allCommands.insertFieldAfter, t("InsertColumnRight"), + menuItemCmd(allCommands.insertFieldAfter, t("Insert column to the right"), disableForReadonlyView), diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts index 3d562fbd..3d746036 100644 --- a/app/client/ui/CustomSectionConfig.ts +++ b/app/client/ui/CustomSectionConfig.ts @@ -61,7 +61,7 @@ class ColumnPicker extends Disposable { return [ cssLabel( this._column.title, - this._column.optional ? cssSubLabel(t('Optional')) : null, + this._column.optional ? cssSubLabel(t(" (optional)")) : null, testId('label-for-' + this._column.name), ), this._column.description ? cssHelp( @@ -73,7 +73,7 @@ class ColumnPicker extends Disposable { properValue, options, { - defaultLabel: this._column.typeDesc != "any" ? t('PickAColumnWithType', {"columnType": this._column.typeDesc}) : t('PickAColumn') + defaultLabel: this._column.typeDesc != "any" ? t("Pick a {{columnType}} column", {"columnType": this._column.typeDesc}) : t("Pick a column") } ), testId('mapping-for-' + this._column.name), @@ -105,7 +105,7 @@ class ColumnListPicker extends Disposable { return [ cssRow( cssAddMapping( - cssAddIcon('Plus'), t('Add') + ' ' + this._column.title, + cssAddIcon('Plus'), t("Add") + ' ' + this._column.title, menu(() => { const otherColumns = this._getNotMappedColumns(); const typedColumns = otherColumns.filter(this._typeFilter()); @@ -370,17 +370,17 @@ export class CustomSectionConfig extends Disposable { return null; } switch(level) { - case AccessLevel.none: return cssConfirmLine(t("WidgetNoPermissison")); - case AccessLevel.read_table: return cssConfirmLine(t("WidgetNeedRead", {read: dom("b", "read")})); // TODO i18next - case AccessLevel.full: return cssConfirmLine(t("WidgetNeedFullAccess", {fullAccess: dom("b", "full access")})); // TODO i18next + case AccessLevel.none: return cssConfirmLine(t("Widget does not require any permissions.")); + case AccessLevel.read_table: return cssConfirmLine(t("Widget needs to {{read}} the current table.", {read: dom("b", "read")})); // TODO i18next + case AccessLevel.full: return cssConfirmLine(t("Widget needs {{fullAccess}} to this document.", {fullAccess: dom("b", "full access")})); // TODO i18next default: throw new Error(`Unsupported ${level} access level`); } } // Options for access level. const levels: IOptionFull[] = [ - {label: t('NoDocumentAccess'), value: AccessLevel.none}, - {label: t('ReadSelectedTable'), value: AccessLevel.read_table}, - {label: t('FullDocumentAccess'), value: AccessLevel.full}, + {label: t("No document access"), value: AccessLevel.none}, + {label: t("Read selected table"), value: AccessLevel.read_table}, + {label: t("Full document access"), value: AccessLevel.full}, ]; return dom( 'div', @@ -388,7 +388,7 @@ export class CustomSectionConfig extends Disposable { this._canSelect ? cssRow( select(this._selectedId, options, { - defaultLabel: t('SelectCustomWidget'), + defaultLabel: t("Select Custom Widget"), menuCssClass: cssMenu.className, }), testId('select') @@ -399,7 +399,7 @@ export class CustomSectionConfig extends Disposable { cssTextInput( this._url, async value => this._url.set(value), - dom.attr('placeholder', t('EnterCustomURL')), + dom.attr('placeholder', t("Enter Custom URL")), testId('url') ) ), @@ -440,7 +440,7 @@ export class CustomSectionConfig extends Disposable { dom.maybe(this._hasConfiguration, () => cssSection( textButton( - t('OpenConfiguration'), + t("Open configuration"), dom.on('click', () => this._openConfiguration()), testId('open-configuration') ) @@ -450,7 +450,7 @@ export class CustomSectionConfig extends Disposable { cssLink( dom.attr('href', 'https://support.getgrist.com/widget-custom'), dom.attr('target', '_blank'), - t('LearnMore') + t("Learn more about custom widgets") ) ), dom.maybeOwned(use => use(this._section.columnsToMap), (owner, columns) => { diff --git a/app/client/ui/DocHistory.ts b/app/client/ui/DocHistory.ts index 587ec1c5..55c2f1a8 100644 --- a/app/client/ui/DocHistory.ts +++ b/app/client/ui/DocHistory.ts @@ -28,8 +28,8 @@ export class DocHistory extends Disposable implements IDomComponent { public buildDom() { const tabs = [ - {value: 'activity', label: t('Activity')}, - {value: 'snapshots', label: t('Snapshots')}, + {value: 'activity', label: t("Activity")}, + {value: 'snapshots', label: t("Snapshots")}, ]; return [ cssSubTabs( @@ -79,7 +79,7 @@ export class DocHistory extends Disposable implements IDomComponent { return dom( 'div', dom.maybe(snapshotsDenied, () => cssSnapshotDenied( - t('SnapshotsUnavailable'), + t("Snapshots are unavailable."), testId('doc-history-error'))), // Note that most recent snapshots are first. dom.domComputed(snapshots, (snapshotList) => snapshotList.map((snapshot, index) => { @@ -98,11 +98,11 @@ export class DocHistory extends Disposable implements IDomComponent { ), cssMenuDots(icon('Dots'), menu(() => [ - menuItemLink(setLink(snapshot), t('OpenSnapshot')), - menuItemLink(setLink(snapshot, origUrlId), t('CompareToCurrent'), - menuAnnotate(t('Beta'))), - prevSnapshot && menuItemLink(setLink(prevSnapshot, snapshot.docId), t('CompareToPrevious'), - menuAnnotate(t('Beta'))), + menuItemLink(setLink(snapshot), t("Open Snapshot")), + menuItemLink(setLink(snapshot, origUrlId), t("Compare to Current"), + menuAnnotate(t("Beta"))), + prevSnapshot && menuItemLink(setLink(prevSnapshot, snapshot.docId), t("Compare to Previous"), + menuAnnotate(t("Beta"))), ], {placement: 'bottom-end', parentSelectorToMark: '.' + cssSnapshotCard.className} ), diff --git a/app/client/ui/DocMenu.ts b/app/client/ui/DocMenu.ts index c96c9a63..2c143d3c 100644 --- a/app/client/ui/DocMenu.ts +++ b/app/client/ui/DocMenu.ts @@ -71,8 +71,8 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { return css.docList( css.docMenu( dom.maybe(!home.app.currentFeatures.workspaces, () => [ - css.docListHeader(t('ServiceNotAvailable')), - dom('span', t('NeedPaidPlan')), + css.docListHeader(t("This service is not available right now")), + dom('span', t("(The organization needs a paid plan)")), ]), // currentWS and showIntro observables change together. We capture both in one domComputed call. @@ -98,7 +98,7 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { // TODO: this is shown on all pages, but there is a hack in currentWSPinnedDocs that // removes all pinned docs when on trash page. dom.maybe((use) => use(home.currentWSPinnedDocs).length > 0, () => [ - css.docListHeader(css.pinnedDocsIcon('PinBig'), t('PinnedDocuments')), + css.docListHeader(css.pinnedDocsIcon('PinBig'), t("Pinned Documents")), createPinnedDocs(home, home.currentWSPinnedDocs), ]), @@ -106,7 +106,7 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { dom.maybe((use) => page === 'templates' && use(home.featuredTemplates).length > 0, () => [ css.featuredTemplatesHeader( css.featuredTemplatesIcon('Idea'), - t('Featured'), + t("Featured"), testId('featured-templates-header') ), createPinnedDocs(home, home.featuredTemplates, true), @@ -118,12 +118,12 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { null : css.docListHeader( ( - page === 'all' ? t('AllDocuments') : + page === 'all' ? t("All Documents") : page === 'templates' ? dom.domComputed(use => use(home.featuredTemplates).length > 0, (hasFeaturedTemplates) => - hasFeaturedTemplates ? t('MoreExamplesAndTemplates') : t('ExamplesAndTemplates') + hasFeaturedTemplates ? t("More Examples and Templates") : t("Examples and Templates") ) : - page === 'trash' ? t('Trash') : + page === 'trash' ? t("Trash") : workspace && [css.docHeaderIcon('Folder'), workspaceName(home.app, workspace)] ), testId('doc-header'), @@ -138,9 +138,9 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { ) : (page === 'trash') ? dom('div', - css.docBlock(t('DocStayInTrash')), + css.docBlock(t("Documents stay in Trash for 30 days, after which they get deleted permanently.")), dom.maybe((use) => use(home.trashWorkspaces).length === 0, () => - css.docBlock(t("EmptyTrash")) + css.docBlock(t("Trash is empty.")) ), buildAllDocsBlock(home, home.trashWorkspaces, false, flashDocId, viewSettings), ) : @@ -155,7 +155,7 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { ) : workspace && !workspace.isSupportWorkspace && workspace.docs?.length === 0 ? buildWorkspaceIntro(home) : - css.docBlock(t('WorkspaceNotFound')) + css.docBlock(t("Workspace not found")) ) ]), ]; @@ -187,7 +187,7 @@ function buildAllDocsBlock( (ws.removedAt ? [ - css.docRowUpdatedAt(t('Deleted', {at:getTimeFromNow(ws.removedAt)})), + css.docRowUpdatedAt(t("Deleted {{at}}", {at:getTimeFromNow(ws.removedAt)})), css.docMenuTrigger(icon('Dots')), menu(() => makeRemovedWsOptionsMenu(home, ws), {placement: 'bottom-end', parentSelectorToMark: '.' + css.docRowWrapper.className}), @@ -221,7 +221,7 @@ function buildAllDocsTemplates(home: HomeModel, viewSettings: ViewSettings) { dom.autoDispose(hideTemplatesObs), css.templatesHeaderWrap( css.templatesHeader( - t('Examples&Templates'), + t("Examples & Templates"), dom.domComputed(hideTemplatesObs, (collapsed) => collapsed ? css.templatesHeaderIcon('Expand') : css.templatesHeaderIcon('Collapse') ), @@ -233,7 +233,7 @@ function buildAllDocsTemplates(home: HomeModel, viewSettings: ViewSettings) { dom.maybe((use) => !use(hideTemplatesObs), () => [ buildTemplateDocs(home, templates, viewSettings), bigBasicButton( - t('DiscoverMoreTemplates'), + t("Discover More Templates"), urlState().setLinkUrl({homePage: 'templates'}), testId('all-docs-templates-discover-more'), ) @@ -281,7 +281,7 @@ function buildOtherSites(home: HomeModel) { return css.otherSitesBlock( dom.autoDispose(hideOtherSitesObs), css.otherSitesHeader( - t('OtherSites'), + t("Other Sites"), dom.domComputed(hideOtherSitesObs, (collapsed) => collapsed ? css.otherSitesHeaderIcon('Expand') : css.otherSitesHeaderIcon('Collapse') ), @@ -293,7 +293,7 @@ function buildOtherSites(home: HomeModel) { const siteName = home.app.currentOrgName; return [ dom('div', - t('OtherSitesWelcome', { siteName, context: personal ? 'personal' : '' }), + t("You are on the {{siteName}} site. You also have access to the following sites:", { siteName, context: personal ? 'personal' : '' }), testId('other-sites-message') ), css.otherSitesButtons( @@ -329,8 +329,8 @@ function buildPrefs( // The Sort selector. options.hideSort ? null : dom.update( select(viewSettings.currentSort, [ - {value: 'name', label: t('ByName')}, - {value: 'date', label: t('ByDateModified')}, + {value: 'name', label: t("By Name")}, + {value: 'date', label: t("By Date Modified")}, ], { buttonCssClass: css.sortSelector.className }, ), @@ -386,8 +386,8 @@ function buildWorkspaceDocBlock(home: HomeModel, workspace: Workspace, flashDocI ), css.docRowUpdatedAt( (doc.removedAt ? - t('Deleted', {at: getTimeFromNow(doc.removedAt)}) : - t('Edited', {at: getTimeFromNow(doc.updatedAt)})), + t("Deleted {{at}}", {at: getTimeFromNow(doc.removedAt)}) : + t("Edited {{at}}", {at: getTimeFromNow(doc.updatedAt)})), testId('doc-time') ), (doc.removedAt ? @@ -421,7 +421,7 @@ function buildWorkspaceDocBlock(home: HomeModel, workspace: Workspace, flashDocI save: (val) => doRename(home, doc, val, flashDocId), close: () => renaming.set(null), }, testId('doc-name-editor')), - css.docRowUpdatedAt(t('Edited', {at: getTimeFromNow(doc.updatedAt)}), testId('doc-time')), + css.docRowUpdatedAt(t("Edited {{at}}", {at: getTimeFromNow(doc.updatedAt)}), testId('doc-time')), ), ), testId('doc') @@ -462,9 +462,9 @@ export function makeDocOptionsMenu(home: HomeModel, doc: Document, renaming: Obs const orgAccess: roles.Role|null = org ? org.access : null; function deleteDoc() { - confirmModal(t('DeleteDoc', {name: doc.name}), t('Delete'), + confirmModal(t("Delete {{name}}", {name: doc.name}), t("Delete"), () => home.deleteDoc(doc.id, false).catch(reportError), - t('DocumentMoveToTrash')); + t("Document will be moved to Trash.")); } async function manageUsers() { @@ -487,7 +487,7 @@ export function makeDocOptionsMenu(home: HomeModel, doc: Document, renaming: Obs dom.cls('disabled', !roles.canEdit(doc.access)), testId('rename-doc') ), - menuItem(() => showMoveDocModal(home, doc), t('Move'), + menuItem(() => showMoveDocModal(home, doc), t("Move"), // Note that moving the doc requires ACL access on the doc. Moving a doc to a workspace // that confers descendant ACL access could otherwise increase the user's access to the doc. // By requiring the user to have ACL edit access on the doc to move it prevents using this @@ -498,16 +498,16 @@ export function makeDocOptionsMenu(home: HomeModel, doc: Document, renaming: Obs dom.cls('disabled', !roles.canEditAccess(doc.access)), testId('move-doc') ), - menuItem(deleteDoc, t('Remove'), + menuItem(deleteDoc, t("Remove"), dom.cls('disabled', !roles.isOwner(doc)), testId('delete-doc') ), menuItem(() => home.pinUnpinDoc(doc.id, !doc.isPinned).catch(reportError), - doc.isPinned ? t("UnpinDocument"): t("PinDocument"), + doc.isPinned ? t("Unpin Document"): t("Pin Document"), dom.cls('disabled', !roles.canEdit(orgAccess)), testId('pin-doc') ), - menuItem(manageUsers, roles.canEditAccess(doc.access) ? t("ManageUsers"): t("AccessDetails"), + menuItem(manageUsers, roles.canEditAccess(doc.access) ? t("Manage Users"): t("Access Details"), testId('doc-access') ) ]; @@ -515,22 +515,22 @@ export function makeDocOptionsMenu(home: HomeModel, doc: Document, renaming: Obs export function makeRemovedDocOptionsMenu(home: HomeModel, doc: Document, workspace: Workspace) { function hardDeleteDoc() { - confirmModal(t("DeleteForeverDoc", {name: doc.name}), t("DeleteForever"), + confirmModal(t("Permanently Delete \"{{name}}\"?", {name: doc.name}), t("Delete Forever"), () => home.deleteDoc(doc.id, true).catch(reportError), - t('DeleteDocPerma')); + t("Document will be permanently deleted.")); } return [ - menuItem(() => home.restoreDoc(doc), t('Restore'), + menuItem(() => home.restoreDoc(doc), t("Restore"), dom.cls('disabled', !roles.isOwner(doc) || !!workspace.removedAt), testId('doc-restore') ), - menuItem(hardDeleteDoc, t('DeleteForever'), + menuItem(hardDeleteDoc, t("Delete Forever"), dom.cls('disabled', !roles.isOwner(doc)), testId('doc-delete-forever') ), (workspace.removedAt ? - menuText(t('RestoreThisDocument')) : + menuText(t("To restore this document, restore the workspace first.")) : null ) ]; @@ -538,16 +538,16 @@ export function makeRemovedDocOptionsMenu(home: HomeModel, doc: Document, worksp function makeRemovedWsOptionsMenu(home: HomeModel, ws: Workspace) { return [ - menuItem(() => home.restoreWorkspace(ws), t('Restore'), + menuItem(() => home.restoreWorkspace(ws), t("Restore"), dom.cls('disabled', !roles.canDelete(ws.access)), testId('ws-restore') ), - menuItem(() => home.deleteWorkspace(ws.id, true), t('DeleteForever'), + menuItem(() => home.deleteWorkspace(ws.id, true), t("Delete Forever"), dom.cls('disabled', !roles.canDelete(ws.access) || ws.docs.length > 0), testId('ws-delete-forever') ), (ws.docs.length > 0 ? - menuText(t('DeleteWorkspaceForever')) : + menuText(t("You may delete a workspace forever once it has no documents in it.")) : null ) ]; @@ -565,8 +565,8 @@ function showMoveDocModal(home: HomeModel, doc: Document) { const disabled = isCurrent || !isEditable; return css.moveDocListItem( css.moveDocListText(workspaceName(home.app, ws)), - isCurrent ? css.moveDocListHintText(t('CurrentWorkspace')) : null, - !isEditable ? css.moveDocListHintText(t('RequiresEditPermissions')) : null, + isCurrent ? css.moveDocListHintText(t("Current workspace")) : null, + !isEditable ? css.moveDocListHintText(t("Requires edit permissions")) : null, css.moveDocListItem.cls('-disabled', disabled), css.moveDocListItem.cls('-selected', (use) => use(selected) === ws.id), dom.on('click', () => disabled || selected.set(ws.id)), @@ -576,11 +576,11 @@ function showMoveDocModal(home: HomeModel, doc: Document) { ) ); return { - title: t('MoveDocToWorkspace', {name: doc.name}), + title: t("Move {{name}} to workspace", {name: doc.name}), body, saveDisabled: Computed.create(owner, (use) => !use(selected)), saveFunc: async () => !selected.get() || home.moveDoc(doc.id, selected.get()!).catch(reportError), - saveLabel: t('Move'), + saveLabel: t("Move"), }; }); } diff --git a/app/client/ui/DocTour.ts b/app/client/ui/DocTour.ts index 7732d342..27351d91 100644 --- a/app/client/ui/DocTour.ts +++ b/app/client/ui/DocTour.ts @@ -20,8 +20,8 @@ export async function startDocTour(docData: DocData, docComm: DocComm, onFinishC } const invalidDocTour: IOnBoardingMsg[] = [{ - title: t('InvalidDocTourTitle'), - body: t('InvalidDocTourBody'), + title: t("No valid document tour"), + body: t("Cannot construct a document tour from the data in this document. Ensure there is a table named GristDocTour with columns Title, Body, Placement, and Location."), selector: 'document', showHasModal: true, }]; diff --git a/app/client/ui/DocumentSettings.ts b/app/client/ui/DocumentSettings.ts index afd079fa..bbe3bd08 100644 --- a/app/client/ui/DocumentSettings.ts +++ b/app/client/ui/DocumentSettings.ts @@ -42,23 +42,23 @@ export async function showDocSettingsModal(docInfo: DocInfoRec, docPageModel: Do const canChangeEngine = getSupportedEngineChoices().length > 0; return { - title: t('DocumentSettings'), + title: t("Document Settings"), body: [ - cssDataRow(t('ThisDocumentID')), + cssDataRow(t("This document's ID (for API use):")), cssDataRow(dom('tt', docPageModel.currentDocId.get())), - cssDataRow(t('TimeZone')), + cssDataRow(t("Time Zone:")), cssDataRow(dom.create(buildTZAutocomplete, moment, timezoneObs, (val) => timezoneObs.set(val))), - cssDataRow(t('Locale')), + cssDataRow(t("Locale:")), cssDataRow(dom.create(buildLocaleSelect, localeObs)), - cssDataRow(t('Currency')), + cssDataRow(t("Currency:")), cssDataRow(dom.domComputed(localeObs, (l) => dom.create(buildCurrencyPicker, currencyObs, (val) => currencyObs.set(val), - {defaultCurrencyLabel: t('LocalCurrency', {currency: getCurrency(l)})}) + {defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})}) )), canChangeEngine ? [ // Small easter egg: you can click on the skull-and-crossbones to // force a reload of the document. - cssDataRow(t('EngineRisk', {span: + cssDataRow(t("Engine (experimental {{span}} change at own risk):", {span: dom('span', '☠', dom.style('cursor', 'pointer'), dom.on('click', async () => { @@ -71,7 +71,7 @@ export async function showDocSettingsModal(docInfo: DocInfoRec, docPageModel: Do ], // Modal label is "Save", unless engine is changed. If engine is changed, the document will // need a reload to switch engines, so we replace the label with "Save and Reload". - saveLabel: dom.text((use) => (use(engineObs) === docSettings.engine) ? t('Save') : t('SaveAndReload')), + saveLabel: dom.text((use) => (use(engineObs) === docSettings.engine) ? t("Save") : t("Save and Reload")), saveFunc: async () => { await docInfo.updateColValues({ timezone: timezoneObs.get(), diff --git a/app/client/ui/DuplicateTable.ts b/app/client/ui/DuplicateTable.ts index f5894599..7fda7b4e 100644 --- a/app/client/ui/DuplicateTable.ts +++ b/app/client/ui/DuplicateTable.ts @@ -74,7 +74,7 @@ class DuplicateTableModal extends Disposable { input( this._newTableName, {onInput: true}, - {placeholder: t('NewName')}, + {placeholder: t("Name for new table")}, (elem) => { setTimeout(() => { elem.focus(); }, 20); }, dom.on('focus', (_ev, elem) => { elem.select(); }), dom.cls(cssInput.className), @@ -85,19 +85,19 @@ class DuplicateTableModal extends Disposable { cssWarningIcon('Warning'), dom('div', - t("AdviceWithLink", {link: cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, 'Read More.')}) + t("Instead of duplicating tables, it's usually better to segment data using linked views. {{link}}", {link: cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, 'Read More.')}) ), //TODO: i18next ), cssField( cssCheckbox( this._includeData, - t('CopyAllData'), + t("Copy all data in addition to the table structure."), testId('copy-all-data'), ), ), dom.maybe(this._includeData, () => cssWarning( cssWarningIcon('Warning'), - dom('div', t('WarningACL')), + dom('div', t("Only the document default access rules will apply to the copy.")), testId('acl-warning'), )), ]; diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 3e410f9f..8a34e01f 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -54,7 +54,7 @@ export function buildNameConfig( }; return [ - cssLabel(t('ColumnLabel')), + cssLabel(t("COLUMN LABEL AND ID")), cssRow( dom.cls(cssBlockedCursor.className, origColumn.disableModify), cssColLabelBlock( @@ -84,7 +84,7 @@ export function buildNameConfig( ) ), dom.maybe(isSummaryTable, - () => cssRow(t('ColumnOptionsLimited'))) + () => cssRow(t("Column options are limited in summary tables."))) ]; } @@ -250,7 +250,7 @@ export function buildFormulaConfig( // Clears the column const clearAndResetOption = () => selectOption( () => gristDoc.clearColumns([origColumn.id.peek()]), - t('ClearAndReset'), 'CrossSmall'); + t("Clear and reset"), 'CrossSmall'); // Actions on text buttons: @@ -314,7 +314,7 @@ export function buildFormulaConfig( cssRow(formulaField = buildFormula( origColumn, buildEditor, - t('EnterFormula'), + t("Enter formula"), disableOtherActions, onSave, clearState)), @@ -322,21 +322,21 @@ export function buildFormulaConfig( ]; return dom.maybe(behavior, (type: BEHAVIOR) => [ - cssLabel(t('ColumnBehavior')), + cssLabel(t("COLUMN BEHAVIOR")), ...(type === "empty" ? [ menu(behaviorLabel(), [ convertToDataOption(), ]), cssEmptySeparator(), cssRow(textButton( - t('SetFormula'), + t("Set formula"), dom.on("click", setFormula), dom.prop("disabled", disableOtherActions), testId("field-set-formula") )), cssRow(withInfoTooltip( textButton( - t('SetTriggerFormula'), + t("Set trigger formula"), dom.on("click", setTrigger), dom.prop("disabled", use => use(isSummaryTable) || use(disableOtherActions)), testId("field-set-trigger") @@ -344,7 +344,7 @@ export function buildFormulaConfig( GristTooltips.setTriggerFormula(), )), cssRow(textButton( - t('MakeIntoDataColumn'), + t("Make into data column"), dom.on("click", convertToData), dom.prop("disabled", use => use(isSummaryTable) || use(disableOtherActions)), testId("field-set-data") @@ -377,7 +377,7 @@ export function buildFormulaConfig( ), // If data column is or wants to be a trigger formula: dom.maybe((use) => use(maybeTrigger) || use(origColumn.hasTriggerFormula), () => [ - cssLabel(t('TriggerFormula')), + cssLabel(t("TRIGGER FORMULA")), formulaBuilder(onSaveConvertToTrigger), dom.create(buildFormulaTriggers, origColumn, { disabled: disableOtherActions, @@ -389,7 +389,7 @@ export function buildFormulaConfig( cssEmptySeparator(), cssRow(withInfoTooltip( textButton( - t("SetTriggerFormula"), + t("Set trigger formula"), dom.on("click", convertDataColumnToTriggerColumn), dom.prop("disabled", disableOtherActions), testId("field-set-trigger") diff --git a/app/client/ui/FilterConfig.ts b/app/client/ui/FilterConfig.ts index 07a97178..388ea30e 100644 --- a/app/client/ui/FilterConfig.ts +++ b/app/client/ui/FilterConfig.ts @@ -87,7 +87,7 @@ export class FilterConfig extends Disposable { dom.domComputed((use) => { const filters = use(this._section.filters); return cssTextBtn( - t('AddColumn'), + t("Add Column"), addFilterMenu(filters, this._popupControls, { menuOptions: { placement: 'bottom-end', diff --git a/app/client/ui/GridOptions.ts b/app/client/ui/GridOptions.ts index 57c232ec..2afc1180 100644 --- a/app/client/ui/GridOptions.ts +++ b/app/client/ui/GridOptions.ts @@ -20,23 +20,23 @@ export class GridOptions extends Disposable { public buildDom() { const section = this._section; return [ - cssLabel(t('GridOptions')), + cssLabel(t("Grid Options")), dom('div', [ cssRow( checkbox(setSaveValueFromKo(this, section.optionsObj.prop('verticalGridlines'))), - t('VerticalGridlines'), + t("Vertical Gridlines"), testId('v-grid-button') ), cssRow( checkbox(setSaveValueFromKo(this, section.optionsObj.prop('horizontalGridlines'))), - t('HorizontalGridlines'), + t("Horizontal Gridlines"), testId('h-grid-button') ), cssRow( checkbox(setSaveValueFromKo(this, section.optionsObj.prop('zebraStripes'))), - t('ZebraStripes'), + t("Zebra Stripes"), testId('zebra-stripe-button') ), diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts index 8ee582c3..7d7f777c 100644 --- a/app/client/ui/GridViewMenus.ts +++ b/app/client/ui/GridViewMenus.ts @@ -26,13 +26,13 @@ interface IViewSection { */ export function ColumnAddMenu(gridView: IView, viewSection: IViewSection) { return [ - menuItem(() => gridView.addNewColumn(), t('AddColumn')), + menuItem(() => gridView.addNewColumn(), t("Add Column")), menuDivider(), ...viewSection.hiddenColumns().map((col: any) => menuItem( () => { gridView.showColumn(col.id(), viewSection.viewFields().peekLength); // .then(() => gridView.scrollPaneRight()); - }, t('ShowColumn', {label: col.label()}))) + }, t("Show column {{- label}}", {label: col.label()}))) ]; } export interface IMultiColumnContextMenu { @@ -68,13 +68,13 @@ export function ColumnContextMenu(options: IColumnContextMenu) { const addToSortLabel = getAddToSortLabel(sortSpec, colId); return [ - menuItemCmd(allCommands.fieldTabOpen, t('ColumnOptions')), - menuItem(filterOpenFunc, t('FilterData')), + menuItemCmd(allCommands.fieldTabOpen, t("Column Options")), + menuItem(filterOpenFunc, t("Filter Data")), menuDivider({style: 'margin-bottom: 0;'}), cssRowMenuItem( customMenuItem( allCommands.sortAsc.run, - dom('span', t('Sort'), {style: 'flex: 1 0 auto; margin-right: 8px;'}, + dom('span', t("Sort"), {style: 'flex: 1 0 auto; margin-right: 8px;'}, testId('sort-label')), icon('Sort', dom.style('transform', 'scaley(-1)')), 'A-Z', @@ -112,9 +112,9 @@ export function ColumnContextMenu(options: IColumnContextMenu) { ), ] : null, menuDivider({style: 'margin-bottom: 0; margin-top: 0;'}), - menuItem(allCommands.sortFilterTabOpen.run, t('MoreSortOptions'), testId('more-sort-options')), + menuItem(allCommands.sortFilterTabOpen.run, t("More sort options ..."), testId('more-sort-options')), menuDivider({style: 'margin-top: 0;'}), - menuItemCmd(allCommands.renameField, t('RenameColumn'), disableForReadonlyColumn), + menuItemCmd(allCommands.renameField, t("Rename column"), disableForReadonlyColumn), freezeMenuItemCmd(options), menuDivider(), MultiColumnMenu((options.disableFrozenMenu = true, options)), @@ -144,20 +144,20 @@ export function MultiColumnMenu(options: IMultiColumnContextMenu) { frozenMenu ? [frozenMenu, menuDivider()]: null, // Offered only when selection includes formula columns, and converts only those. (options.isFormula ? - menuItemCmd(allCommands.convertFormulasToData, t('ConvertFormulaToData'), + menuItemCmd(allCommands.convertFormulasToData, t("Convert formula to data"), disableForReadonlyColumn) : null), // With data columns selected, offer an additional option to clear out selected cells. (options.isFormula !== true ? - menuItemCmd(allCommands.clearValues, t('ClearValues'), disableForReadonlyColumn) : null), + menuItemCmd(allCommands.clearValues, t("Clear values"), disableForReadonlyColumn) : null), (!options.isRaw ? menuItemCmd(allCommands.hideFields, nameHideColumns, disableForReadonlyView) : null), menuItemCmd(allCommands.clearColumns, nameClearColumns, disableForReadonlyColumn), menuItemCmd(allCommands.deleteFields, nameDeleteColumns, disableForReadonlyColumn), menuDivider(), - menuItemCmd(allCommands.insertFieldBefore, t('InsertColumn', {to: 'left'}), disableForReadonlyView), - menuItemCmd(allCommands.insertFieldAfter, t('InsertColumn', {to: 'right'}), disableForReadonlyView) + menuItemCmd(allCommands.insertFieldBefore, t("Insert column to the {{to}}", {to: 'left'}), disableForReadonlyView), + menuItemCmd(allCommands.insertFieldAfter, t("Insert column to the {{to}}", {to: 'right'}), disableForReadonlyView) ]; } @@ -278,9 +278,9 @@ function getAddToSortLabel(sortSpec: Sort.SortSpec, colId: number): string|undef if (sortSpec.length !== 0 && !isEqual(columnsInSpec, [colId])) { const index = columnsInSpec.indexOf(colId); if (index > -1) { - return t('AddToSort', {count: index + 1, context: 'added'}); + return t("Add to sort", {count: index + 1, context: 'added'}); } else { - return t('AddToSort'); + return t("Add to sort"); } } } diff --git a/app/client/ui/HomeIntro.ts b/app/client/ui/HomeIntro.ts index 6cc98756..b775d473 100644 --- a/app/client/ui/HomeIntro.ts +++ b/app/client/ui/HomeIntro.ts @@ -37,7 +37,7 @@ export function buildHomeIntro(homeModel: HomeModel): DomContents { export function buildWorkspaceIntro(homeModel: HomeModel): DomContents { const isViewer = homeModel.currentWS.get()?.access === roles.VIEWER; const isAnonym = !homeModel.app.currentValidUser; - const emptyLine = cssIntroLine(testId('empty-workspace-info'), t('EmptyWorkspace')); + const emptyLine = cssIntroLine(testId('empty-workspace-info'), t("This workspace is empty.")); if (isAnonym || isViewer) { return emptyLine; } else { @@ -58,38 +58,38 @@ function makeViewerTeamSiteIntro(homeModel: HomeModel) { const docLink = (dom.maybe(personalOrg, org => { return cssLink( urlState().setLinkUrl({org: org.domain ?? undefined}), - t('PersonalSite'), + t("personal site"), testId('welcome-personal-url')); })); return [ css.docListHeader( dom.autoDispose(personalOrg), - t('WelcomeTo', {orgName: homeModel.app.currentOrgName}), + t("Welcome to {{orgName}}", {orgName: homeModel.app.currentOrgName}), productPill(homeModel.app.currentOrg, {large: true}), testId('welcome-title') ), cssIntroLine( testId('welcome-info'), - t('WelcomeInfoNoDocuments'), + t("You have read-only access to this site. Currently there are no documents."), dom('br'), - t('WelcomeInfoAppearHere'), + t("Any documents created in this site will appear here."), ), cssIntroLine( - t('WelcomeTextVistGrist'), docLink, '.', + t("Interested in using Grist outside of your team? Visit your free "), docLink, '.', testId('welcome-text') ) ]; } function makeTeamSiteIntro(homeModel: HomeModel) { - const sproutsProgram = cssLink({href: commonUrls.sproutsProgram, target: '_blank'}, t('SproutsProgram')); + const sproutsProgram = cssLink({href: commonUrls.sproutsProgram, target: '_blank'}, t("Sprouts Program")); return [ css.docListHeader( - t('WelcomeTo', {orgName: homeModel.app.currentOrgName}), + t("Welcome to {{orgName}}", {orgName: homeModel.app.currentOrgName}), productPill(homeModel.app.currentOrg, {large: true}), testId('welcome-title') ), - cssIntroLine(t('TeamSiteIntroGetStarted')), + cssIntroLine(t("Get started by inviting your team and creating your first Grist document.")), (shouldHideUiElement('helpCenter') ? null : cssIntroLine( 'Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', // TODO i18n @@ -102,10 +102,10 @@ function makeTeamSiteIntro(homeModel: HomeModel) { function makePersonalIntro(homeModel: HomeModel, user: FullUser) { return [ - css.docListHeader(t('WelcomeUser', {name: user.name}), testId('welcome-title')), - cssIntroLine(t('PersonalIntroGetStarted')), + css.docListHeader(t("Welcome to Grist, {{name}}!", {name: user.name}), testId('welcome-title')), + cssIntroLine(t("Get started by creating your first Grist document.")), (shouldHideUiElement('helpCenter') ? null : - cssIntroLine(t('VisitHelpCenter', { link: helpCenterLink() }), + cssIntroLine(t("Visit our {{link}} to learn more.", { link: helpCenterLink() }), testId('welcome-text')) ), makeCreateButtons(homeModel), @@ -113,19 +113,19 @@ function makePersonalIntro(homeModel: HomeModel, user: FullUser) { } function makeAnonIntro(homeModel: HomeModel) { - const signUp = cssLink({href: getLoginOrSignupUrl()}, t('SignUp')); + const signUp = cssLink({href: getLoginOrSignupUrl()}, t("Sign up")); return [ - css.docListHeader(t('Welcome'), testId('welcome-title')), - cssIntroLine(t('AnonIntroGetStarted')), + 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 - (shouldHideUiElement('helpCenter') ? null : t('VisitHelpCenter', { link: helpCenterLink() })), + (shouldHideUiElement('helpCenter') ? null : t("Visit our {{link}} to learn more.", { link: helpCenterLink() })), testId('welcome-text')), makeCreateButtons(homeModel), ]; } function helpCenterLink() { - return cssLink({href: commonUrls.help, target: '_blank'}, cssInlineIcon('Help'), t('HelpCenter')); + return cssLink({href: commonUrls.help, target: '_blank'}, cssInlineIcon('Help'), t("Help Center")); } function buildButtons(homeModel: HomeModel, options: { @@ -136,22 +136,22 @@ function buildButtons(homeModel: HomeModel, options: { }) { return cssBtnGroup( !options.invite ? null : - cssBtn(cssBtnIcon('Help'), t('InviteTeamMembers'), testId('intro-invite'), + cssBtn(cssBtnIcon('Help'), t("Invite Team Members"), testId('intro-invite'), cssButton.cls('-primary'), dom.on('click', () => manageTeamUsersApp(homeModel.app)), ), !options.templates ? null : - cssBtn(cssBtnIcon('FieldTable'), t('BrowseTemplates'), testId('intro-templates'), + cssBtn(cssBtnIcon('FieldTable'), t("Browse Templates"), testId('intro-templates'), cssButton.cls('-primary'), dom.hide(shouldHideUiElement("templates")), urlState().setLinkUrl({homePage: 'templates'}), ), !options.import ? null : - cssBtn(cssBtnIcon('Import'), t('ImportDocument'), testId('intro-import-doc'), + cssBtn(cssBtnIcon('Import'), t("Import Document"), testId('intro-import-doc'), dom.on('click', () => importDocAndOpen(homeModel)), ), !options.empty ? null : - cssBtn(cssBtnIcon('Page'), t('CreateEmptyDocument'), testId('intro-create-doc'), + cssBtn(cssBtnIcon('Page'), t("Create Empty Document"), testId('intro-create-doc'), dom.on('click', () => createDocAndOpen(homeModel)), ), ); diff --git a/app/client/ui/HomeLeftPane.ts b/app/client/ui/HomeLeftPane.ts index c3c03530..edc90c9e 100644 --- a/app/client/ui/HomeLeftPane.ts +++ b/app/client/ui/HomeLeftPane.ts @@ -42,14 +42,14 @@ export function createHomeLeftPane(leftPanelOpen: Observable, home: Hom cssPageEntry( cssPageEntry.cls('-selected', (use) => use(home.currentPage) === "all"), cssPageLink(cssPageIcon('Home'), - cssLinkText(t('AllDocuments')), + cssLinkText(t("All Documents")), urlState().setLinkUrl({ws: undefined, homePage: undefined}), testId('dm-all-docs'), ), ), dom.maybe(use => !use(home.singleWorkspace), () => cssSectionHeader( - t('Workspaces'), + t("Workspaces"), // Give it a testId, because it's a good element to simulate "click-away" in tests. testId('dm-ws-label') ), @@ -108,7 +108,7 @@ export function createHomeLeftPane(leftPanelOpen: Observable, home: Hom cssPageEntry( dom.hide(shouldHideUiElement("templates")), cssPageEntry.cls('-selected', (use) => use(home.currentPage) === "templates"), - cssPageLink(cssPageIcon('FieldTable'), cssLinkText(t("ExamplesAndTemplates")), + cssPageLink(cssPageIcon('FieldTable'), cssLinkText(t("Examples & Templates")), urlState().setLinkUrl({homePage: "templates"}), testId('dm-templates-page'), ), @@ -176,11 +176,11 @@ function addMenu(home: HomeModel, creating: Observable): DomElementArg[ const needUpgrade = home.app.currentFeatures.maxWorkspacesPerOrg === 1; return [ - menuItem(() => createDocAndOpen(home), menuIcon('Page'), t("CreateEmptyDocument"), + menuItem(() => createDocAndOpen(home), menuIcon('Page'), t("Create Empty Document"), dom.cls('disabled', !home.newDocWorkspace.get()), testId("dm-new-doc") ), - menuItem(() => importDocAndOpen(home), menuIcon('Import'), t("ImportDocument"), + menuItem(() => importDocAndOpen(home), menuIcon('Import'), t("Import Document"), dom.cls('disabled', !home.newDocWorkspace.get()), testId("dm-import") ), @@ -195,7 +195,7 @@ function addMenu(home: HomeModel, creating: Observable): DomElementArg[ ])), // For workspaces: if ACL says we can create them, but product says we can't, // then offer an upgrade link. - upgradableMenuItem(needUpgrade, () => creating.set(true), menuIcon('Folder'), t("CreateWorkspace"), + upgradableMenuItem(needUpgrade, () => creating.set(true), menuIcon('Folder'), t("Create Workspace"), dom.cls('disabled', (use) => !roles.canEdit(orgAccess) || !use(home.available)), testId("dm-new-workspace") ), @@ -205,9 +205,9 @@ function addMenu(home: HomeModel, creating: Observable): DomElementArg[ function workspaceMenu(home: HomeModel, ws: Workspace, renaming: Observable) { function deleteWorkspace() { - confirmModal(t('WorkspaceDeleteTitle', {workspace: ws.name}), t('Delete'), + confirmModal(t("Delete {{workspace}} and all included documents?", {workspace: ws.name}), t("Delete"), () => home.deleteWorkspace(ws.id, false), - t('WorkspaceDeleteText')); + t("Workspace will be moved to Trash.")); } async function manageWorkspaceUsers() { @@ -235,7 +235,7 @@ function workspaceMenu(home: HomeModel, ws: Workspace, renaming: Observable home.app.showUpgradeModal()), ]; diff --git a/app/client/ui/LeftPanelCommon.ts b/app/client/ui/LeftPanelCommon.ts index 83dc21ab..dbb23351 100644 --- a/app/client/ui/LeftPanelCommon.ts +++ b/app/client/ui/LeftPanelCommon.ts @@ -34,7 +34,7 @@ export function createHelpTools(appModel: AppModel): DomContents { return cssSplitPageEntry( cssPageEntryMain( cssPageLink(cssPageIcon('Help'), - cssLinkText(t('HelpCenter')), + cssLinkText(t("Help Center")), dom.cls('tour-help-center'), dom.on('click', (ev) => beaconOpenMessage({appModel})), testId('left-feedback'), diff --git a/app/client/ui/MakeCopyMenu.ts b/app/client/ui/MakeCopyMenu.ts index 1cd7c1e0..c1fd288a 100644 --- a/app/client/ui/MakeCopyMenu.ts +++ b/app/client/ui/MakeCopyMenu.ts @@ -26,29 +26,29 @@ export async function replaceTrunkWithFork(user: FullUser|null, doc: Document, a const trunkAccess = (await app.api.getDoc(origUrlId)).access; if (!roles.canEdit(trunkAccess)) { modal((ctl) => [ - cssModalBody(t('CannotEditOriginal')), + cssModalBody(t("Replacing the original requires editing rights on the original document.")), cssModalButtons( - bigBasicButton(t('Cancel'), dom.on('click', () => ctl.close())), + bigBasicButton(t("Cancel"), dom.on('click', () => ctl.close())), ) ]); return; } const docApi = app.api.getDocAPI(origUrlId); const cmp = await docApi.compareDoc(doc.id); - let titleText = t('UpdateOriginal'); - let buttonText = t('Update'); - let warningText = t('WarningOriginalWillBeUpdated'); + let titleText = t("Update Original"); + let buttonText = t("Update"); + let warningText = t("The original version of this document will be updated."); if (cmp.summary === 'left' || cmp.summary === 'both') { - titleText = t('OriginalHasModifications'); - buttonText = t('Overwrite'); - warningText = `${warningText} ${t('WarningOverwriteOriginalChanges')}`; + titleText = t("Original Has Modifications"); + buttonText = t("Overwrite"); + warningText = `${warningText} ${t("Be careful, the original has changes not in this document. Those changes will be overwritten.")}`; } else if (cmp.summary === 'unrelated') { - titleText = t('OriginalLooksUnrelated'); - buttonText = t('Overwrite'); - warningText = `${warningText} ${t('WarningWillBeOverwritten')}`; + titleText = t("Original Looks Unrelated"); + buttonText = t("Overwrite"); + warningText = `${warningText} ${t("It will be overwritten, losing any content not in this document.")}`; } else if (cmp.summary === 'same') { titleText = 'Original Looks Identical'; - warningText = `${warningText} ${t('WarningAlreadyIdentical')}`; + warningText = `${warningText} ${t("However, it appears to be already identical.")}`; } confirmModal(titleText, buttonText, async () => { @@ -66,8 +66,8 @@ function signupModal(message: string) { return modal((ctl) => [ cssModalBody(message), cssModalButtons( - bigPrimaryButtonLink(t('SignUp'), {href: getLoginOrSignupUrl(), target: '_blank'}, testId('modal-signup')), - bigBasicButton(t('Cancel'), dom.on('click', () => ctl.close())), + bigPrimaryButtonLink(t("Sign up"), {href: getLoginOrSignupUrl(), target: '_blank'}, testId('modal-signup')), + bigBasicButton(t("Cancel"), dom.on('click', () => ctl.close())), ), cssModalWidth('normal'), ]); @@ -96,7 +96,7 @@ function allowOtherOrgs(doc: Document, app: AppModel): boolean { */ export async function makeCopy(doc: Document, app: AppModel, modalTitle: string): Promise { if (!app.currentValidUser) { - signupModal(t('ToSaveSignUpAndReload')); + signupModal(t("To save your changes, please sign up, then reload this page.")); return; } let orgs = allowOtherOrgs(doc, app) ? await app.api.getOrgs(true) : null; @@ -150,7 +150,7 @@ class SaveCopyModal extends Disposable { public async save() { const ws = this._destWS.get(); - if (!ws) { throw new Error(t('NoDestinationWorkspace')); } + if (!ws) { throw new Error(t("No destination workspace")); } const api = this._app.api; const org = this._destOrg.get(); const docWorker = await api.getWorkerAPI('import'); @@ -173,7 +173,7 @@ class SaveCopyModal extends Disposable { return [ cssField( cssLabel(t("Name")), - input(this._destName, {onInput: true}, {placeholder: t('EnterDocumentName')}, dom.cls(cssInput.className), + input(this._destName, {onInput: true}, {placeholder: t("Enter document name")}, dom.cls(cssInput.className), // modal dialog grabs focus after 10ms delay; so to focus this input, wait a bit longer // (see the TODO in app/client/ui2018/modals.ts about weasel.js and focus). (elem) => { setTimeout(() => { elem.focus(); }, 20); }, @@ -181,8 +181,8 @@ class SaveCopyModal extends Disposable { testId('copy-dest-name')) ), cssField( - cssLabel(t("AsTemplate")), - cssCheckbox(this._asTemplate, t('IncludeStructureWithoutData'), + cssLabel(t("As Template")), + cssCheckbox(this._asTemplate, t("Include the structure without any of the data."), testId('save-as-template')) ), // Show the team picker only when saving to other teams is allowed and there are other teams @@ -199,7 +199,7 @@ class SaveCopyModal extends Disposable { // Show the workspace picker only when destOrg is a team site, because personal orgs do not have workspaces. dom.domComputed((use) => use(this._showWorkspaces) && use(this._workspaces), (wss) => wss === false ? null : - wss && wss.length === 0 ? cssWarningText(t("NoWriteAccessToSite"), + wss && wss.length === 0 ? cssWarningText(t("You do not have write access to this site"), testId('copy-warning')) : [ cssField( @@ -216,7 +216,7 @@ class SaveCopyModal extends Disposable { ), wss ? dom.domComputed(this._destWS, (destWs) => destWs && !roles.canEdit(destWs.access) ? - cssWarningText(t("NoWriteAccessToWorkspace"), + cssWarningText(t("You do not have write access to the selected workspace"), testId('copy-warning') ) : null ) : null diff --git a/app/client/ui/NotifyUI.ts b/app/client/ui/NotifyUI.ts index b9d44587..7236c50a 100644 --- a/app/client/ui/NotifyUI.ts +++ b/app/client/ui/NotifyUI.ts @@ -24,10 +24,10 @@ function buildAction(action: NotifyAction, item: Notification, options: IBeaconO switch (action) { case 'upgrade': if (appModel) { - return cssToastAction(t('UpgradePlan'), dom.on('click', () => + return cssToastAction(t("Upgrade Plan"), dom.on('click', () => appModel.showUpgradeModal())); } else { - return dom('a', cssToastAction.cls(''), t('UpgradePlan'), {target: '_blank'}, + return dom('a', cssToastAction.cls(''), t("Upgrade Plan"), {target: '_blank'}, {href: commonUrls.plans}); } case 'renew': @@ -37,22 +37,22 @@ function buildAction(action: NotifyAction, item: Notification, options: IBeaconO if (appModel && appModel.currentOrg && appModel.currentOrg.billingAccount && !appModel.currentOrg.billingAccount.isManager) { return null; } // Otherwise return a link to the billing page. - return dom('a', cssToastAction.cls(''), t('Renew'), {target: '_blank'}, + return dom('a', cssToastAction.cls(''), t("Renew"), {target: '_blank'}, {href: urlState().makeUrl({billing: 'billing'})}); case 'personal': if (!appModel) { return null; } - return cssToastAction(t('GoToPersonalSite'), dom.on('click', async () => { + return cssToastAction(t("Go to your free personal site"), dom.on('click', async () => { const info = await appModel.api.getSessionAll(); const orgs = info.orgs.filter(org => org.owner && org.owner.id === appModel.currentUser?.id); if (orgs.length !== 1) { - throw new Error(t('ErrorCannotFindPersonalSite')); + throw new Error(t("Cannot find personal site, sorry!")); } window.location.assign(urlState().makeUrl({org: orgs[0].domain || undefined})); })); case 'report-problem': - return cssToastAction(t('ReportProblem'), testId('toast-report-problem'), + return cssToastAction(t("Report a problem"), testId('toast-report-problem'), dom.on('click', () => beaconOpenMessage({...options, includeAppErrors: true}))); case 'ask-for-help': { @@ -60,7 +60,7 @@ function buildAction(action: NotifyAction, item: Notification, options: IBeaconO error: new Error(item.options.message as string), timestamp: item.options.timestamp, }]; - return cssToastAction(t('AskForHelp'), + return cssToastAction(t("Ask for help"), dom.on('click', () => beaconOpenMessage({...options, includeAppErrors: true, errors}))); } @@ -154,11 +154,11 @@ function buildNotifyDropdown(ctl: IOpenController, notifier: Notifier, appModel: cssDropdownContent( cssDropdownHeader( - cssDropdownHeaderTitle(t('Notifications')), + cssDropdownHeaderTitle(t("Notifications")), shouldHideUiElement("helpCenter") ? null : cssDropdownFeedbackLink( cssDropdownFeedbackIcon('Feedback'), - t('GiveFeedback'), + t("Give feedback"), dom.on('click', () => beaconOpenMessage({appModel, onOpen: () => ctl.close(), route: '/ask/message/'})), testId('feedback'), ) @@ -171,7 +171,7 @@ function buildNotifyDropdown(ctl: IOpenController, notifier: Notifier, appModel: ), dom.maybe((use) => use(dropdownItems).length === 0 && !use(disconnectMsg), () => cssDropdownStatus( - dom('div', cssDropdownStatusText(t('NoNotifications'))), + dom('div', cssDropdownStatusText(t("No notifications"))), ) ), dom.forEach(dropdownItems, item => diff --git a/app/client/ui/OnBoardingPopups.ts b/app/client/ui/OnBoardingPopups.ts index e8a2f354..20840ef4 100644 --- a/app/client/ui/OnBoardingPopups.ts +++ b/app/client/ui/OnBoardingPopups.ts @@ -299,7 +299,7 @@ class OnBoardingPopupsCtl extends Disposable { {style: `margin-right: 8px; visibility: ${isFirstStep ? 'hidden' : 'visible'}`}, ), bigPrimaryButton( - isLastStep ? t('Finish') : t('Next'), testId('next'), + isLastStep ? t("Finish") : t("Next"), testId('next'), dom.on('click', () => this._move(+1, true)), ), ) diff --git a/app/client/ui/OpenVideoTour.ts b/app/client/ui/OpenVideoTour.ts index 0f4206da..a8308b0d 100644 --- a/app/client/ui/OpenVideoTour.ts +++ b/app/client/ui/OpenVideoTour.ts @@ -28,7 +28,7 @@ const testId = makeTestId('test-video-tour-'); cssVideo( { src: commonUrls.videoTour, - title: t('YouTubeVideoPlayer'), + title: t("YouTube video player"), frameborder: '0', allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture', allowfullscreen: '', @@ -51,7 +51,7 @@ const testId = makeTestId('test-video-tour-'); export function createVideoTourTextButton(): HTMLDivElement { const elem: HTMLDivElement = cssVideoTourTextButton( cssVideoIcon('Video'), - t('GristVideoTour'), + t("Grist Video Tour"), dom.on('click', () => openVideoTour(elem)), testId('text-button'), ); @@ -77,7 +77,7 @@ export function createVideoTourToolsButton(): HTMLDivElement | null { dom.autoDispose(commandsGroup), cssPageLink( iconElement = cssPageIcon('Video'), - cssLinkText(t('VideoTour')), + cssLinkText(t("Video Tour")), dom.cls('tour-help-center'), dom.on('click', () => openVideoTour(iconElement)), testId('tools-button'), diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts index a2f873fd..b0b9cfbc 100644 --- a/app/client/ui/PageWidgetPicker.ts +++ b/app/client/ui/PageWidgetPicker.ts @@ -183,7 +183,7 @@ export function buildPageWidgetPicker( // should be handle by the caller. if (await isLongerThan(savePromise, DELAY_BEFORE_SPINNER_MS)) { const label = getWidgetTypes(type).label; - await spinnerModal(t('BuildingWidget', { label }), savePromise); + await spinnerModal(t("Building {{- label}} widget", { label }), savePromise); } } } @@ -285,7 +285,7 @@ export class PageWidgetSelect extends Disposable { testId('container'), cssBody( cssPanel( - header(t('SelectWidget')), + header(t("Select Widget")), sectionTypes.map((value) => { const {label, icon: iconName} = getWidgetTypes(value); const disabled = computed(this._value.table, (use, tid) => this._isTypeDisabled(value, tid)); @@ -302,7 +302,7 @@ export class PageWidgetSelect extends Disposable { ), cssPanel( testId('data'), - header(t('SelectData')), + header(t("Select Data")), cssEntry( cssIcon('TypeTable'), 'New Table', // prevent the selection of 'New Table' if it is disabled @@ -336,7 +336,7 @@ export class PageWidgetSelect extends Disposable { )), ), cssPanel( - header(t('GroupBy')), + header(t("Group by")), dom.hide((use) => !use(this._value.summarize)), domComputed( (use) => use(this._columns) @@ -378,7 +378,7 @@ export class PageWidgetSelect extends Disposable { bigPrimaryButton( // TODO: The button's label of the page widget picker should read 'Close' instead when // there are no changes. - this._options.buttonLabel || t('AddToPage'), + this._options.buttonLabel || t("Add to Page"), dom.prop('disabled', (use) => !isValidSelection( use(this._value.table), use(this._value.type), this._options.isNewPage) ), diff --git a/app/client/ui/Pages.ts b/app/client/ui/Pages.ts index 0021a284..b6f97bc9 100644 --- a/app/client/ui/Pages.ts +++ b/app/client/ui/Pages.ts @@ -142,7 +142,7 @@ function buildPrompt(tableNames: string[], onSave: (option: RemoveOption) => Pro testId('popup'), buildWarning(tableNames), cssOptions( - buildOption(selected, 'data', t('DeleteDataAndPage')), + buildOption(selected, 'data', t("Delete data and this page.")), buildOption(selected, 'page', [ // TODO i18n `Keep data and delete page. `, @@ -153,7 +153,7 @@ function buildPrompt(tableNames: string[], onSave: (option: RemoveOption) => Pro ) ), saveDisabled, - saveLabel: t('Delete'), + saveLabel: t("Delete"), saveFunc, width: 'fixed-wide', extraButtons: [], diff --git a/app/client/ui/RightPanel.ts b/app/client/ui/RightPanel.ts index 76849f04..793bc699 100644 --- a/app/client/ui/RightPanel.ts +++ b/app/client/ui/RightPanel.ts @@ -242,7 +242,7 @@ export class RightPanel extends Disposable { ), cssSeparator(), dom.maybe(fieldBuilder, builder => [ - cssLabel(t('ColumnType')), + cssLabel(t("COLUMN TYPE")), cssSection( builder.buildSelectTypeDom(), ), @@ -265,7 +265,7 @@ export class RightPanel extends Disposable { cssRow(refSelect.buildDom()), cssSeparator() ]), - cssLabel(t('Transform')), + cssLabel(t("TRANSFORM")), dom.maybe(fieldBuilder, builder => builder.buildTransformDom()), dom.maybe(isMultiSelect, () => disabledSection()), testId('panel-transform'), @@ -295,15 +295,15 @@ export class RightPanel extends Disposable { private _buildPageWidgetContent(_owner: MultiHolder) { return [ cssSubTabContainer( - cssSubTab(t('Widget'), + cssSubTab(t("Widget"), cssSubTab.cls('-selected', (use) => use(this._subTab) === 'widget'), dom.on('click', () => this._subTab.set("widget")), testId('config-widget')), - cssSubTab(t('SortAndFilter'), + cssSubTab(t("Sort & Filter"), cssSubTab.cls('-selected', (use) => use(this._subTab) === 'sortAndFilter'), dom.on('click', () => this._subTab.set("sortAndFilter")), testId('config-sortAndFilter')), - cssSubTab(t('Data'), + cssSubTab(t("Data"), cssSubTab.cls('-selected', (use) => use(this._subTab) === 'data'), dom.on('click', () => this._subTab.set("data")), testId('config-data')), @@ -345,7 +345,7 @@ export class RightPanel extends Disposable { }); return dom.maybe(viewConfigTab, (vct) => [ this._disableIfReadonly(), - cssLabel(dom.text(use => use(activeSection.isRaw) ? t('DataTableName') : t('WidgetTitle')), + cssLabel(dom.text(use => use(activeSection.isRaw) ? t("DATA TABLE NAME") : t("WIDGET TITLE")), dom.style('margin-bottom', '14px'), ), cssRow(cssTextInput( @@ -362,7 +362,7 @@ export class RightPanel extends Disposable { dom.maybe( (use) => !use(activeSection.isRaw), () => cssRow( - primaryButton(t('ChangeWidget'), this._createPageWidgetPicker()), + primaryButton(t("Change Widget"), this._createPageWidgetPicker()), cssRow.cls('-top-space') ), ), @@ -370,7 +370,7 @@ export class RightPanel extends Disposable { cssSeparator(), dom.maybe((use) => ['detail', 'single'].includes(use(this._pageWidgetType)!), () => [ - cssLabel(t('Theme')), + cssLabel(t("Theme")), dom('div', vct._buildThemeDom(), vct._buildLayoutDom()) @@ -385,22 +385,22 @@ export class RightPanel extends Disposable { if (use(this._pageWidgetType) !== 'record') { return null; } return [ cssSeparator(), - cssLabel(t('RowStyleUpper')), + cssLabel(t("ROW STYLE")), domAsync(imports.loadViewPane().then(ViewPane => - dom.create(ViewPane.ConditionalStyle, t("RowStyle"), activeSection, this._gristDoc) + dom.create(ViewPane.ConditionalStyle, t("Row Style"), activeSection, this._gristDoc) )) ]; }), dom.maybe((use) => use(this._pageWidgetType) === 'chart', () => [ - cssLabel(t('ChartType')), + cssLabel(t("CHART TYPE")), vct._buildChartConfigDom(), ]), dom.maybe((use) => use(this._pageWidgetType) === 'custom', () => { const parts = vct._buildCustomTypeItems() as any[]; return [ - cssLabel(t('Custom')), + cssLabel(t("CUSTOM")), // If 'customViewPlugin' feature is on, show the toggle that allows switching to // plugin mode. Note that the default mode for a new 'custom' view is 'url', so that's // the only one that will be shown without the feature flag. @@ -465,15 +465,15 @@ export class RightPanel extends Disposable { link.onWrite((val) => this._gristDoc.saveLink(val)); return [ this._disableIfReadonly(), - cssLabel(t('DataTable')), + cssLabel(t("DATA TABLE")), cssRow( - cssIcon('TypeTable'), cssDataLabel(t('SourceData')), + cssIcon('TypeTable'), cssDataLabel(t("SOURCE DATA")), cssContent(dom.text((use) => use(use(table).primaryTableId)), testId('pwc-table')) ), dom( 'div', - cssRow(cssIcon('Pivot'), cssDataLabel(t('GroupedBy'))), + cssRow(cssIcon('Pivot'), cssDataLabel(t("GROUPED BY"))), cssRow(domComputed(groupedBy, (cols) => cssList(cols.map((c) => ( cssListItem(dom.text(c.label), testId('pwc-groupedBy-col')) @@ -485,12 +485,12 @@ export class RightPanel extends Disposable { ), dom.maybe((use) => !use(activeSection.isRaw), () => - cssButtonRow(primaryButton(t('EditDataSelection'), this._createPageWidgetPicker(), + cssButtonRow(primaryButton(t("Edit Data Selection"), this._createPageWidgetPicker(), testId('pwc-editDataSelection')), dom.maybe( use => Boolean(use(use(activeSection.table).summarySourceTable)), () => basicButton( - t('Detach'), + t("Detach"), dom.on('click', () => this._gristDoc.docData.sendAction( ["DetachSummaryViewSection", activeSection.getRowId()])), testId('detach-button'), @@ -507,10 +507,10 @@ export class RightPanel extends Disposable { cssSeparator(), dom.maybe((use) => !use(activeSection.isRaw), () => [ - cssLabel(t('SelectBy')), + cssLabel(t("SELECT BY")), cssRow( dom.update( - select(link, linkOptions, {defaultLabel: t('SelectWidget')}), + select(link, linkOptions, {defaultLabel: t("Select Widget")}), dom.on('click', () => { refreshTrigger.set(!refreshTrigger.get()); }) @@ -526,7 +526,7 @@ export class RightPanel extends Disposable { // TODO: sections should be listed following the order of appearance in the view layout (ie: // left/right - top/bottom); return selectorFor.length ? [ - cssLabel(t('SelectorFor'), testId('selector-for')), + cssLabel(t("SELECTOR FOR"), testId('selector-for')), cssRow(cssList(selectorFor.map((sec) => this._buildSectionItem(sec)))) ] : null; }), @@ -538,7 +538,7 @@ export class RightPanel extends Disposable { const section = gristDoc.viewModel.activeSection; const onSave = (val: IPageWidget) => gristDoc.saveViewSection(section.peek(), val); return (elem) => { attachPageWidgetPicker(elem, gristDoc, onSave, { - buttonLabel: t('Save'), + buttonLabel: t("Save"), value: () => toPageWidget(section.peek()), selectBy: (val) => gristDoc.selectBy(val), }); }; @@ -559,7 +559,7 @@ export class RightPanel extends Disposable { return dom.maybe(this._gristDoc.docPageModel.isReadonly, () => ( cssOverlay( testId('disable-overlay'), - cssBottomText(t('NoEditAccess')), + cssBottomText(t("You do not have edit access to this document")), ) )); } diff --git a/app/client/ui/RowContextMenu.ts b/app/client/ui/RowContextMenu.ts index cdc7cfb5..f63b68ab 100644 --- a/app/client/ui/RowContextMenu.ts +++ b/app/client/ui/RowContextMenu.ts @@ -19,14 +19,14 @@ export function RowContextMenu({ disableInsert, disableDelete, isViewSorted, num // bottom. It could be very confusing for users who might expect the record to stay above or // below the active row. Thus in this case we show a single `insert row` command. result.push( - menuItemCmd(allCommands.insertRecordAfter, t('InsertRow'), + menuItemCmd(allCommands.insertRecordAfter, t("Insert row"), dom.cls('disabled', disableInsert)), ); } else { result.push( - menuItemCmd(allCommands.insertRecordBefore, t('InsertRowAbove'), + menuItemCmd(allCommands.insertRecordBefore, t("Insert row above"), dom.cls('disabled', disableInsert)), - menuItemCmd(allCommands.insertRecordAfter, t('InsertRowBelow'), + menuItemCmd(allCommands.insertRecordAfter, t("Insert row below"), dom.cls('disabled', disableInsert)), ); } @@ -37,11 +37,11 @@ export function RowContextMenu({ disableInsert, disableDelete, isViewSorted, num result.push( menuDivider(), // TODO: should show `Delete ${num} rows` when multiple are selected - menuItemCmd(allCommands.deleteRecords, t('Delete'), + menuItemCmd(allCommands.deleteRecords, t("Delete"), dom.cls('disabled', disableDelete)), ); result.push( menuDivider(), - menuItemCmd(allCommands.copyLink, t('CopyAnchorLink'))); + menuItemCmd(allCommands.copyLink, t("Copy anchor link"))); return result; } diff --git a/app/client/ui/ShareMenu.ts b/app/client/ui/ShareMenu.ts index c4bd257c..1c65bd9f 100644 --- a/app/client/ui/ShareMenu.ts +++ b/app/client/ui/ShareMenu.ts @@ -35,18 +35,18 @@ export function buildShareMenuButton(pageModel: DocPageModel): DomContents { // available (a user quick enough to open the menu in this state would have to re-open it). return dom.maybe(pageModel.currentDoc, (doc) => { const appModel = pageModel.appModel; - const saveCopy = () => makeCopy(doc, appModel, t('SaveDocument')).catch(reportError); + const saveCopy = () => makeCopy(doc, appModel, t("Save Document")).catch(reportError); if (doc.idParts.snapshotId) { const backToCurrent = () => urlState().pushUrl({doc: buildOriginalUrlId(doc.id, true)}); - return shareButton(t('BackToCurrent'), () => [ + return shareButton(t("Back to Current"), () => [ menuManageUsers(doc, pageModel), - menuSaveCopy(t('SaveCopy'), doc, appModel), + menuSaveCopy(t("Save Copy"), doc, appModel), menuOriginal(doc, appModel, true), menuExports(doc, pageModel), ], {buttonAction: backToCurrent}); } else if (doc.isPreFork || doc.isBareFork) { // A new unsaved document, or a fiddle, or a public example. - const saveActionTitle = doc.isBareFork ? t('SaveDocument') : t('SaveCopy'); + const saveActionTitle = doc.isBareFork ? t("Save Document") : t("Save Copy"); return shareButton(saveActionTitle, () => [ menuManageUsers(doc, pageModel), menuSaveCopy(saveActionTitle, doc, appModel), @@ -58,16 +58,16 @@ export function buildShareMenuButton(pageModel: DocPageModel): DomContents { // Copy" primary and keep it as an action button on top. Otherwise, show a tag without a // default action; click opens the menu where the user can choose. if (!roles.canEdit(doc.trunkAccess || null)) { - return shareButton(t('SaveCopy'), () => [ + return shareButton(t("Save Copy"), () => [ menuManageUsers(doc, pageModel), - menuSaveCopy(t('SaveCopy'), doc, appModel), + menuSaveCopy(t("Save Copy"), doc, appModel), menuOriginal(doc, appModel, false), menuExports(doc, pageModel), ], {buttonAction: saveCopy}); } else { - return shareButton(t('Unsaved'), () => [ + return shareButton(t("Unsaved"), () => [ menuManageUsers(doc, pageModel), - menuSaveCopy(t('SaveCopy'), doc, appModel), + menuSaveCopy(t("Save Copy"), doc, appModel), menuOriginal(doc, appModel, false), menuExports(doc, pageModel), ]); @@ -75,7 +75,7 @@ export function buildShareMenuButton(pageModel: DocPageModel): DomContents { } else { return shareButton(null, () => [ menuManageUsers(doc, pageModel), - menuSaveCopy(t('DuplicateDocument'), doc, appModel), + menuSaveCopy(t("Duplicate Document"), doc, appModel), menuWorkOnCopy(pageModel), menuExports(doc, pageModel), ]); @@ -132,7 +132,7 @@ function shareButton(buttonText: string|null, menuCreateFunc: MenuCreateFunc, function menuManageUsers(doc: DocInfo, pageModel: DocPageModel) { return [ menuItem(() => manageUsers(doc, pageModel), - roles.canEditAccess(doc.access) ? t('ManageUsers') : t('AccessDetails'), + roles.canEditAccess(doc.access) ? t("Manage Users") : t("Access Details"), dom.cls('disabled', doc.isFork), testId('tb-share-option') ), @@ -143,7 +143,7 @@ function menuManageUsers(doc: DocInfo, pageModel: DocPageModel) { // Renders "Return to Original" and "Replace Original" menu items. When used with snapshots, we // say "Current Version" in place of the word "Original". function menuOriginal(doc: Document, appModel: AppModel, isSnapshot: boolean) { - const termToUse = isSnapshot ? t("CurrentVersion") : t("Original"); + const termToUse = isSnapshot ? t("Current Version") : t("Original"); const origUrlId = buildOriginalUrlId(doc.id, isSnapshot); const originalUrl = urlState().makeUrl({doc: origUrlId}); @@ -166,18 +166,18 @@ function menuOriginal(doc: Document, appModel: AppModel, isSnapshot: boolean) { } return [ cssMenuSplitLink({href: originalUrl}, - cssMenuSplitLinkText(t('ReturnToTermToUse', {termToUse})), testId('return-to-original'), + cssMenuSplitLinkText(t("Return to {{termToUse}}", {termToUse})), testId('return-to-original'), cssMenuIconLink({href: originalUrl, target: '_blank'}, testId('open-original'), cssMenuIcon('FieldLink'), ) ), - menuItem(replaceOriginal, t('ReplaceTermToUse', {termToUse}), + menuItem(replaceOriginal, t("Replace {{termToUse}}...", {termToUse}), // Disable if original is not writable, and also when comparing snapshots (since it's // unclear which of the versions to use). dom.cls('disabled', !roles.canEdit(doc.trunkAccess || null) || comparingSnapshots), testId('replace-original'), ), - menuItemLink(compareHref, {target: '_blank'}, t('CompareTermToUse', {termToUse}), + menuItemLink(compareHref, {target: '_blank'}, t("Compare to {{termToUse}}", {termToUse}), menuAnnotate('Beta'), testId('compare-original'), ), @@ -205,10 +205,10 @@ function menuWorkOnCopy(pageModel: DocPageModel) { }; return [ - menuItem(makeUnsavedCopy, t('WorkOnCopy'), testId('work-on-copy')), + menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')), menuText( withInfoTooltip( - t('EditWithoutAffecting'), + t("Edit without affecting the original"), GristTooltips.workOnACopy(), {tooltipMenuOptions: {attach: null}} ) @@ -229,21 +229,21 @@ function menuExports(doc: Document, pageModel: DocPageModel) { menuDivider(), (isElectron ? menuItem(() => gristDoc.app.comm.showItemInFolder(doc.name), - t('ShowInFolder'), testId('tb-share-option')) : + t("Show in folder"), testId('tb-share-option')) : menuItemLink({ href: pageModel.appModel.api.getDocAPI(doc.id).getDownloadUrl(), target: '_blank', download: '' }, - menuIcon('Download'), t('Download'), testId('tb-share-option')) + menuIcon('Download'), t("Download"), testId('tb-share-option')) ), menuItemLink({ href: gristDoc.getCsvLink(), target: '_blank', download: ''}, - menuIcon('Download'), t('ExportCSV'), testId('tb-share-option')), + menuIcon('Download'), t("Export CSV"), testId('tb-share-option')), menuItemLink({ href: pageModel.appModel.api.getDocAPI(doc.id).getDownloadXlsxUrl(), target: '_blank', download: '' - }, menuIcon('Download'), t('ExportXLSX'), testId('tb-share-option')), + }, menuIcon('Download'), t("Export XLSX"), testId('tb-share-option')), menuItem(() => sendToDrive(doc, pageModel), - menuIcon('Download'), t('SendToGoogleDrive'), testId('tb-share-option')), + menuIcon('Download'), t("Send to Google Drive"), testId('tb-share-option')), ]; } diff --git a/app/client/ui/SiteSwitcher.ts b/app/client/ui/SiteSwitcher.ts index 41483f30..af8b00dc 100644 --- a/app/client/ui/SiteSwitcher.ts +++ b/app/client/ui/SiteSwitcher.ts @@ -33,7 +33,7 @@ export function buildSiteSwitcher(appModel: AppModel) { const orgs = appModel.topAppModel.orgs; return [ - menuSubHeader(t('SwitchSites')), + menuSubHeader(t("Switch Sites")), dom.forEach(orgs, (org) => menuItemLink(urlState().setLinkUrl({ org: org.domain || undefined }), cssOrgSelected.cls('', appModel.currentOrg ? org.id === appModel.currentOrg.id : false), @@ -45,7 +45,7 @@ export function buildSiteSwitcher(appModel: AppModel) { menuItem( () => appModel.showNewSiteModal(), menuIcon('Plus'), - t('CreateNewTeamSite'), + t("Create new team site"), testId('create-new-site'), ), ]; diff --git a/app/client/ui/SortConfig.ts b/app/client/ui/SortConfig.ts index 2d235654..ab23d848 100644 --- a/app/client/ui/SortConfig.ts +++ b/app/client/ui/SortConfig.ts @@ -149,9 +149,9 @@ export class SortConfig extends Disposable { }); return {computed, allowedTypes, flag, label}; }; - const orderByChoice = computedFlag('orderByChoice', ['Choice'], t('UseChoicePosition')); - const naturalSort = computedFlag('naturalSort', ['Text'], t('NaturalSort')); - const emptyLast = computedFlag('emptyLast', null, t('EmptyValuesLast')); + const orderByChoice = computedFlag('orderByChoice', ['Choice'], t("Use choice position")); + const naturalSort = computedFlag('naturalSort', ['Text'], t("Natural sort")); + const emptyLast = computedFlag('emptyLast', null, t("Empty values last")); const flags = [orderByChoice, emptyLast, naturalSort]; const column = columns.get().find(c => c.value === Sort.getColRef(colRef)); @@ -222,7 +222,7 @@ export class SortConfig extends Disposable { dom.domComputed(use => { const cols = use(available); return cssTextBtn( - t('AddColumn'), + t("Add Column"), menu((ctl) => [ ...cols.map((col) => ( menuItem( @@ -249,7 +249,7 @@ export class SortConfig extends Disposable { private _buildUpdateDataButton() { return dom.maybe(this._section.isSorted, () => cssButtonRow( - cssTextBtn(t('UpdateData'), + cssTextBtn(t("Update Data"), dom.on('click', () => updatePositions(this._gristDoc, this._section)), testId('update'), dom.show((use) => ( diff --git a/app/client/ui/ThemeConfig.ts b/app/client/ui/ThemeConfig.ts index 05ffb488..2501a44d 100644 --- a/app/client/ui/ThemeConfig.ts +++ b/app/client/ui/ThemeConfig.ts @@ -26,7 +26,7 @@ export class ThemeConfig extends Disposable { public buildDom() { return dom('div', - css.subHeader(t('Appearance'), css.betaTag('Beta')), + css.subHeader(t("Appearance "), css.betaTag('Beta')), css.dataRow( cssAppearanceSelect( select( @@ -42,7 +42,7 @@ export class ThemeConfig extends Disposable { css.dataRow( labeledSquareCheckbox( this._syncWithOS, - t('SyncWithOS'), + t("Switch appearance automatically to match system"), testId('sync-with-os'), ), ), diff --git a/app/client/ui/Tools.ts b/app/client/ui/Tools.ts index 9e35a0d4..e23780b5 100644 --- a/app/client/ui/Tools.ts +++ b/app/client/ui/Tools.ts @@ -30,14 +30,14 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse updateCanViewAccessRules(); return cssTools( cssTools.cls('-collapsed', (use) => !use(leftPanelOpen)), - cssSectionHeader(t("Tools")), + cssSectionHeader(t("TOOLS")), cssPageEntry( cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'), cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)), dom.domComputed(canViewAccessRules, (_canViewAccessRules) => { return cssPageLink( cssPageIcon('EyeShow'), - cssLinkText(t('AccessRules'), + cssLinkText(t("Access Rules"), menuAnnotate('Beta', cssBetaTag.cls('')) ), _canViewAccessRules ? urlState().setLinkUrl({docPage: 'acl'}) : null, @@ -49,25 +49,25 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'data'), cssPageLink( cssPageIcon('Database'), - cssLinkText(t('RawData')), + cssLinkText(t("Raw Data")), testId('raw'), urlState().setLinkUrl({docPage: 'data'}) ) ), cssPageEntry( - cssPageLink(cssPageIcon('Log'), cssLinkText(t('DocumentHistory')), testId('log'), + cssPageLink(cssPageIcon('Log'), cssLinkText(t("Document History")), testId('log'), dom.on('click', () => gristDoc.showTool('docHistory'))) ), // TODO: polish validation and add it back dom.maybe((use) => use(gristDoc.app.features).validationsTool, () => cssPageEntry( - cssPageLink(cssPageIcon('Validation'), cssLinkText(t('ValidateData')), testId('validate'), + cssPageLink(cssPageIcon('Validation'), cssLinkText(t("Validate Data")), testId('validate'), dom.on('click', () => gristDoc.showTool('validations')))) ), cssPageEntry( cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'code'), cssPageLink(cssPageIcon('Code'), - cssLinkText(t('CodeView')), + cssLinkText(t("Code View")), urlState().setLinkUrl({docPage: 'code'}) ), testId('code'), @@ -77,7 +77,7 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse const ex = buildExamples().find(e => e.urlId === doc.urlId); if (!ex || !ex.tutorialUrl) { return null; } return cssPageEntry( - cssPageLink(cssPageIcon('Page'), cssLinkText(t('HowToTutorial')), testId('tutorial'), + cssPageLink(cssPageIcon('Page'), cssLinkText(t("How-to Tutorial")), testId('tutorial'), {href: ex.tutorialUrl, target: '_blank'}, cssExampleCardOpener( icon('TypeDetails'), @@ -97,14 +97,14 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse cssSplitPageEntry( cssPageEntryMain( cssPageLink(cssPageIcon('Page'), - cssLinkText(t('DocumentTour')), + cssLinkText(t("Tour of this Document")), urlState().setLinkUrl({docTour: true}), testId('doctour'), ), ), !isDocOwner ? null : cssPageEntrySmall( cssPageLink(cssPageIcon('Remove'), - dom.on('click', () => confirmModal(t('DeleteDocumentTour'), t('Delete'), () => + dom.on('click', () => confirmModal(t("Delete document tour?"), t("Delete"), () => gristDoc.docData.sendAction(['RemoveTable', 'GristDocTour'])) ), testId('remove-doctour') diff --git a/app/client/ui/TopBar.ts b/app/client/ui/TopBar.ts index cd193017..ddc63193 100644 --- a/app/client/ui/TopBar.ts +++ b/app/client/ui/TopBar.ts @@ -29,7 +29,7 @@ export function createTopBarHome(appModel: AppModel) { (appModel.isTeamSite && roles.canEditAccess(appModel.currentOrg?.access || null) ? [ basicButton( - t('ManageTeam'), + t("Manage Team"), dom.on('click', () => manageTeamUsersApp(appModel)), testId('topbar-manage-team') ), diff --git a/app/client/ui/TriggerFormulas.ts b/app/client/ui/TriggerFormulas.ts index a7c2b077..66eeee76 100644 --- a/app/client/ui/TriggerFormulas.ts +++ b/app/client/ui/TriggerFormulas.ts @@ -73,7 +73,7 @@ export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec, opti const docModel = column._table.docModel; const summaryText = Computed.create(owner, use => { if (use(column.recalcWhen) === RecalcWhen.MANUAL_UPDATES) { - return t('AnyField'); + return t("Any field"); } const deps = decodeObject(use(column.recalcDeps)) as number[]|null; if (!deps || deps.length === 0) { return ''; } @@ -98,7 +98,7 @@ export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec, opti cssRow( labeledSquareCheckbox( applyToNew, - t('NewRecords'), + t("Apply to new records"), dom.boolAttr('disabled', newRowsDisabled), testId('field-formula-apply-to-new'), ), @@ -107,8 +107,8 @@ export function buildFormulaTriggers(owner: MultiHolder, column: ColumnRec, opti labeledSquareCheckbox( applyOnChanges, dom.text(use => use(applyOnChanges) ? - t('ChangesTo') : - t('RecordChanges') + t("Apply on changes to:") : + t("Apply on record changes") ), dom.boolAttr('disabled', changesDisabled), testId('field-formula-apply-on-changes'), @@ -200,14 +200,14 @@ function buildTriggerSelectors(ctl: IOpenController, tableRec: TableRec, column: cssItemsFixed( cssSelectorItem( labeledSquareCheckbox(current, - [t('CurrentField'), cssSelectorNote('(data cleaning)')], + [t("Current field "), cssSelectorNote('(data cleaning)')], dom.boolAttr('disabled', allUpdates), ), ), menuDivider(), cssSelectorItem( labeledSquareCheckbox(allUpdates, - [`${t('AnyField')} `, cssSelectorNote('(except formulas)')] + [`${t("Any field")} `, cssSelectorNote('(except formulas)')] ), ), ), @@ -224,12 +224,12 @@ function buildTriggerSelectors(ctl: IOpenController, tableRec: TableRec, column: cssItemsFixed( cssSelectorFooter( dom.maybe(isChanged, () => - primaryButton(t('OK'), + primaryButton(t("OK"), dom.on('click', () => close(true)), testId('trigger-deps-apply') ), ), - basicButton(dom.text(use => use(isChanged) ? t('Cancel') : t('Close')), + basicButton(dom.text(use => use(isChanged) ? t("Cancel") : t("Close")), dom.on('click', () => close(false)), testId('trigger-deps-cancel') ), diff --git a/app/client/ui/ViewLayoutMenu.ts b/app/client/ui/ViewLayoutMenu.ts index 0de2aaad..2df11bc9 100644 --- a/app/client/ui/ViewLayoutMenu.ts +++ b/app/client/ui/ViewLayoutMenu.ts @@ -24,11 +24,11 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool const contextMenu = [ menuItemCmd(allCommands.deleteRecords, - t('DeleteRecord'), + t("Delete record"), testId('section-delete-card'), dom.cls('disabled', isReadonly || isAddRow)), menuItemCmd(allCommands.copyLink, - t('CopyAnchorLink'), + t("Copy anchor link"), testId('section-card-link'), ), menuDivider(), @@ -39,30 +39,30 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool return [ dom.maybe((use) => ['single'].includes(use(viewSection.parentKey)), () => contextMenu), dom.maybe((use) => !use(viewSection.isRaw) && !isLight, - () => menuItemCmd(allCommands.showRawData, t('ShowRawData'), testId('show-raw-data')), + () => menuItemCmd(allCommands.showRawData, t("Show raw data"), testId('show-raw-data')), ), - menuItemCmd(allCommands.printSection, t('PrintWidget'), testId('print-section')), + menuItemCmd(allCommands.printSection, t("Print widget"), testId('print-section')), menuItemLink({ href: gristDoc.getCsvLink(), target: '_blank', download: ''}, - t('DownloadCSV'), testId('download-section')), + t("Download as CSV"), testId('download-section')), menuItemLink({ href: gristDoc.getXlsxActiveViewLink(), target: '_blank', download: ''}, - t('DownloadXLSX'), testId('download-section')), + t("Download as XLSX"), testId('download-section')), dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () => - menuItemCmd(allCommands.editLayout, t('EditCardLayout'), + menuItemCmd(allCommands.editLayout, t("Edit Card Layout"), dom.cls('disabled', isReadonly))), dom.maybe(!isLight, () => [ menuDivider(), - menuItemCmd(allCommands.viewTabOpen, t('WidgetOptions'), testId('widget-options')), - menuItemCmd(allCommands.sortFilterTabOpen, t('AdvancedSortFilter')), - menuItemCmd(allCommands.dataSelectionTabOpen, t('DataSelection')), + menuItemCmd(allCommands.viewTabOpen, t("Widget options"), testId('widget-options')), + menuItemCmd(allCommands.sortFilterTabOpen, t("Advanced Sort & Filter")), + menuItemCmd(allCommands.dataSelectionTabOpen, t("Data selection")), ]), menuDivider(), dom.maybe((use) => use(viewSection.parentKey) === 'custom' && use(viewSection.hasCustomOptions), () => - menuItemCmd(allCommands.openWidgetConfiguration, t('OpenConfiguration'), + menuItemCmd(allCommands.openWidgetConfiguration, t("Open configuration"), testId('section-open-configuration')), ), - menuItemCmd(allCommands.deleteSection, t('DeleteWidget'), + menuItemCmd(allCommands.deleteSection, t("Delete widget"), dom.cls('disabled', !viewRec.getRowId() || viewRec.viewSections().peekLength <= 1 || isReadonly), testId('section-delete')), ]; diff --git a/app/client/ui/ViewSectionMenu.ts b/app/client/ui/ViewSectionMenu.ts index 9a8c97e7..2e3e0ad4 100644 --- a/app/client/ui/ViewSectionMenu.ts +++ b/app/client/ui/ViewSectionMenu.ts @@ -19,7 +19,7 @@ const t = makeT('ViewSectionMenu'); // Handler for [Save] button. async function doSave(docModel: DocModel, viewSection: ViewSectionRec): Promise { - await docModel.docData.bundleActions(t("UpdateSortFilterSettings"), () => Promise.all([ + await docModel.docData.bundleActions(t("Update Sort&Filter settings"), () => Promise.all([ viewSection.activeSortJson.save(), // Save sort viewSection.saveFilters(), // Save filter viewSection.activeCustomOptions.save(), // Save widget options @@ -73,7 +73,7 @@ export function viewSectionMenu( // [Save] [Revert] buttons when there are unsaved options. dom.maybe(displaySaveObs, () => cssSectionSaveButtonsWrapper( cssSaveTextButton( - t('Save'), + t("Save"), cssSaveTextButton.cls('-accent'), dom.on('click', save), hoverTooltip('Save sort & filter settings', {key: 'sortFilterBtnTooltip'}), @@ -99,10 +99,10 @@ export function viewSectionMenu( // [Save] [Revert] buttons dom.domComputed(displaySaveObs, displaySave => [ displaySave ? cssSaveButtonsRow( - cssSaveButton(t('Save'), testId('btn-save'), + cssSaveButton(t("Save"), testId('btn-save'), dom.on('click', () => { ctl.close(); save(); }), dom.boolAttr('disabled', isReadonly)), - basicButton(t('Revert'), testId('btn-revert'), + basicButton(t("Revert"), testId('btn-revert'), dom.on('click', () => { ctl.close(); revert(); })) ) : null, ]), @@ -142,7 +142,7 @@ export function viewSectionMenu( function makeSortPanel(section: ViewSectionRec, gristDoc: GristDoc) { return [ - cssLabel(t('Sort'), testId('heading-sort')), + cssLabel(t("SORT"), testId('heading-sort')), dom.create(SortConfig, section, gristDoc, { // Attach content to triggerElem's parent, which is needed to prevent view // section menu to close when clicking an item in the advanced sort menu. @@ -153,7 +153,7 @@ function makeSortPanel(section: ViewSectionRec, gristDoc: GristDoc) { function makeFilterPanel(section: ViewSectionRec) { return [ - cssLabel(t('Filter'), testId('heading-filter')), + cssLabel(t("FILTER"), testId('heading-filter')), dom.create(FilterConfig, section, { // Attach content to triggerElem's parent, which is needed to prevent view // section menu to close when clicking an item of the add filter menu. @@ -168,13 +168,13 @@ function makeCustomOptions(section: ViewSectionRec) { const color = Computed.create(null, use => use(section.activeCustomOptions.isSaved) ? "-normal" : "-accent"); const text = Computed.create(null, use => { if (use(section.activeCustomOptions)) { - return use(section.activeCustomOptions.isSaved) ? t("Customized") : t("Modified"); + return use(section.activeCustomOptions.isSaved) ? t("(customized)") : t("(modified)"); } else { - return t("Empty"); + return t("(empty)"); } }); return [ - cssMenuInfoHeader(t('CustomOptions'), testId('heading-widget-options')), + cssMenuInfoHeader(t("Custom options"), testId('heading-widget-options')), cssMenuText( dom.autoDispose(text), dom.autoDispose(color), diff --git a/app/client/ui/VisibleFieldsConfig.ts b/app/client/ui/VisibleFieldsConfig.ts index a3941f7d..c52e543a 100644 --- a/app/client/ui/VisibleFieldsConfig.ts +++ b/app/client/ui/VisibleFieldsConfig.ts @@ -163,8 +163,8 @@ export class VisibleFieldsConfig extends Disposable { options.hiddenFields.itemCreateFunc, { itemClass: cssDragRow.className, - reorder() { throw new Error(t('NoReorderHiddenField')); }, - receive() { throw new Error(t('NoDropInHiddenField')); }, + reorder() { throw new Error(t("Hidden Fields cannot be reordered")); }, + receive() { throw new Error(t("Cannot drop items into Hidden Fields")); }, remove(item: ColumnRec) { // Return the column object. This value is passed to the viewFields // receive function as its respective item parameter @@ -204,7 +204,7 @@ export class VisibleFieldsConfig extends Disposable { () => ( cssControlLabel( icon('Tick'), - t('SelectAll'), + t("Select All"), dom.on('click', () => this._setVisibleCheckboxes(fieldsDraggable, true)), testId('visible-fields-select-all'), ) @@ -219,7 +219,7 @@ export class VisibleFieldsConfig extends Disposable { dom.on('click', () => this._removeSelectedFields()), ), basicButton( - t('Clear'), + t("Clear"), dom.on('click', () => this._setVisibleCheckboxes(fieldsDraggable, false)), ), testId('visible-batch-buttons') @@ -240,7 +240,7 @@ export class VisibleFieldsConfig extends Disposable { () => ( cssControlLabel( icon('Tick'), - t('SelectAll'), + t("Select All"), dom.on('click', () => this._setHiddenCheckboxes(hiddenFieldsDraggable, true)), testId('hidden-fields-select-all'), ) @@ -261,7 +261,7 @@ export class VisibleFieldsConfig extends Disposable { dom.on('click', () => this._addSelectedFields()), ), basicButton( - t('Clear'), + t("Clear"), dom.on('click', () => this._setHiddenCheckboxes(hiddenFieldsDraggable, false)), ), testId('hidden-batch-buttons') diff --git a/app/client/ui/WelcomeQuestions.ts b/app/client/ui/WelcomeQuestions.ts index d6463647..8ddbff82 100644 --- a/app/client/ui/WelcomeQuestions.ts +++ b/app/client/ui/WelcomeQuestions.ts @@ -31,7 +31,7 @@ export function showWelcomeQuestions(userPrefsObs: Observable): boole async function onConfirm() { const selected = choices.filter((c, i) => selection[i].get()).map(c => t(c.textKey)); const use_cases = ['L', ...selected]; // Format to populate a ChoiceList column - const use_other = selected.includes(t('Other')) ? otherText.get() : ''; + const use_other = selected.includes(t("Other")) ? otherText.get() : ''; const submitUrl = new URL(window.location.href); submitUrl.pathname = '/welcome/info'; @@ -51,7 +51,7 @@ export function showWelcomeQuestions(userPrefsObs: Observable): boole }); return { - title: [cssLogo(), dom('div', t('WelcomeToGrist'))], + title: [cssLogo(), dom('div', t("Welcome to Grist!"))], body: buildInfoForm(selection, otherText), saveLabel: 'Start using Grist', saveFunc: onConfirm, @@ -79,7 +79,7 @@ const choices: Array<{icon: IconName, color: string, textKey: string}> = [ function buildInfoForm(selection: Observable[], otherText: Observable) { return [ - dom('span', t('WhatBringsYouToGrist')), + dom('span', t("What brings you to Grist? Please help us serve you better.")), cssChoices( choices.map((item, i) => cssChoice( cssIcon(icon(item.icon), {style: `--icon-color: ${item.color}`}), @@ -89,7 +89,7 @@ function buildInfoForm(selection: Observable[], otherText: Observable subscribeElem(elem, selection[i], val => val && setTimeout(() => elem.focus(), 0)), diff --git a/app/client/ui/WidgetTitle.ts b/app/client/ui/WidgetTitle.ts index 8cb9da35..a47425ab 100644 --- a/app/client/ui/WidgetTitle.ts +++ b/app/client/ui/WidgetTitle.ts @@ -67,7 +67,7 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio // Placeholder for widget title: // - when widget title is empty shows a default widget title (what would be shown when title is empty) // - when widget title is set, shows just a text to override it. - const inputWidgetPlaceholder = !vs.title.peek() ? t('OverrideTitle') : vs.defaultWidgetTitle.peek(); + const inputWidgetPlaceholder = !vs.title.peek() ? t("Override widget title") : vs.defaultWidgetTitle.peek(); const disableSave = Computed.create(ctrl, (use) => { const newTableName = use(inputTableName)?.trim() ?? ''; @@ -137,29 +137,29 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio testId('popup'), dom.cls(menuCssClass), dom.maybe(!options.tableNameHidden, () => [ - cssLabel(t('DataTableName')), + cssLabel(t("DATA TABLE NAME")), // Update tableName on key stroke - this will show the default widget name as we type. // above this modal. tableInput = cssInput( inputTableName, updateOnKey, - {disabled: isSummary, placeholder: t('NewTableName')}, + {disabled: isSummary, placeholder: t("Provide a table name")}, testId('table-name-input') ), ]), dom.maybe(!options.widgetNameHidden, () => [ - cssLabel(t('WidgetTitle')), + cssLabel(t("WIDGET TITLE")), widgetInput = cssInput(inputWidgetTitle, updateOnKey, {placeholder: inputWidgetPlaceholder}, testId('section-name-input') ), ]), cssButtons( - primaryButton(t('Save'), + primaryButton(t("Save"), dom.on('click', doSave), dom.boolAttr('disabled', use => use(disableSave) || use(modalCtl.workInProgress)), testId('save'), ), - basicButton(t('Cancel'), + basicButton(t("Cancel"), testId('cancel'), dom.on('click', () => modalCtl.close()) ), diff --git a/app/client/ui/errorPages.ts b/app/client/ui/errorPages.ts index 337f944d..24d7c044 100644 --- a/app/client/ui/errorPages.ts +++ b/app/client/ui/errorPages.ts @@ -28,24 +28,24 @@ export function createErrPage(appModel: AppModel) { * Creates a page to show that the user has no access to this org. */ export function createForbiddenPage(appModel: AppModel, message?: string) { - document.title = t('AccessDenied', {suffix: getPageTitleSuffix(getGristConfig())}); + document.title = t("Access denied{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())}); const isAnonym = () => !appModel.currentValidUser; const isExternal = () => appModel.currentValidUser?.loginMethod === 'External'; - return pagePanelsError(appModel, t('AccessDenied', {suffix: ''}), [ + return pagePanelsError(appModel, t("Access denied{{suffix}}", {suffix: ''}), [ dom.domComputed(appModel.currentValidUser, user => user ? [ - cssErrorText(message || t("DeniedOrganizationDocuments")), - cssErrorText(t("SignInWithDifferentAccount", {email: dom('b', user.email)})), // TODO: i18next + cssErrorText(message || t("You do not have access to this organization's documents.")), + cssErrorText(t("You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.", {email: dom('b', user.email)})), // TODO: i18next ] : [ // This page is not normally shown because a logged out user with no access will get // redirected to log in. But it may be seen if a user logs out and returns to a cached // version of this page or is an external user (connected through GristConnect). - cssErrorText(t("SignInToAccess")), + cssErrorText(t("Sign in to access this organization's documents.")), ]), cssButtonWrap(bigPrimaryButtonLink( - isExternal() ? t("GoToMainPage") : - isAnonym() ? t("SignIn") : - t("AddAcount"), + isExternal() ? t("Go to main page") : + isAnonym() ? t("Sign in") : + t("Add account"), {href: isExternal() ? getMainOrgUrl() : getLoginUrl()}, testId('error-signin'), )) @@ -56,12 +56,12 @@ export function createForbiddenPage(appModel: AppModel, message?: string) { * Creates a page that shows the user is logged out. */ export function createSignedOutPage(appModel: AppModel) { - document.title = t('SignedOut', {suffix: getPageTitleSuffix(getGristConfig())}); + document.title = t("Signed out{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())}); - return pagePanelsError(appModel, t('SignedOut', {suffix: ''}), [ - cssErrorText(t('SignedOutNow')), + return pagePanelsError(appModel, t("Signed out{{suffix}}", {suffix: ''}), [ + cssErrorText(t("You are now signed out.")), cssButtonWrap(bigPrimaryButtonLink( - t('SignedInAgain'), {href: getLoginUrl()}, testId('error-signin') + t("Sign in again"), {href: getLoginUrl()}, testId('error-signin') )) ]); } @@ -70,13 +70,13 @@ export function createSignedOutPage(appModel: AppModel) { * Creates a "Page not found" page. */ export function createNotFoundPage(appModel: AppModel, message?: string) { - document.title = t('PageNotFound', {suffix: getPageTitleSuffix(getGristConfig())}); + document.title = t("Page not found{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())}); - return pagePanelsError(appModel, t('PageNotFound', {suffix: ''}), [ - cssErrorText(message || t('NotFoundMainText', {separator: dom('br')})), // TODO: i18next - cssButtonWrap(bigPrimaryButtonLink(t('GoToMainPage'), testId('error-primary-btn'), + return pagePanelsError(appModel, t("Page not found{{suffix}}", {suffix: ''}), [ + cssErrorText(message || t("The requested page could not be found.{{separator}}Please check the URL and try again.", {separator: dom('br')})), // TODO: i18next + cssButtonWrap(bigPrimaryButtonLink(t("Go to main page"), testId('error-primary-btn'), urlState().setLinkUrl({}))), - cssButtonWrap(bigBasicButtonLink(t('ContactSupport'), {href: 'https://getgrist.com/contact'})), + cssButtonWrap(bigBasicButtonLink(t("Contact support"), {href: 'https://getgrist.com/contact'})), ]); } @@ -84,14 +84,14 @@ export function createNotFoundPage(appModel: AppModel, message?: string) { * Creates a generic error page with the given message. */ export function createOtherErrorPage(appModel: AppModel, message?: string) { - document.title = t('GenericError', {suffix: getPageTitleSuffix(getGristConfig())}); + document.title = t("Error{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())}); - return pagePanelsError(appModel, t('SomethingWentWrong'), [ + return pagePanelsError(appModel, t("Something went wrong"), [ cssErrorText(message ? t('ErrorHappened', {context: 'message', message: addPeriod(message)}) : t('ErrorHappened', {context: 'unknown'})), - cssButtonWrap(bigPrimaryButtonLink(t('GoToMainPage'), testId('error-primary-btn'), + cssButtonWrap(bigPrimaryButtonLink(t("Go to main page"), testId('error-primary-btn'), urlState().setLinkUrl({}))), - cssButtonWrap(bigBasicButtonLink(t('ContactSupport'), {href: 'https://getgrist.com/contact'})), + cssButtonWrap(bigBasicButtonLink(t("Contact support"), {href: 'https://getgrist.com/contact'})), ]); } diff --git a/app/client/ui/sendToDrive.ts b/app/client/ui/sendToDrive.ts index f166ddea..4f9224c7 100644 --- a/app/client/ui/sendToDrive.ts +++ b/app/client/ui/sendToDrive.ts @@ -24,7 +24,7 @@ export async function sendToDrive(doc: Document, pageModel: DocPageModel) { // Create send to google drive handler (it will return a spreadsheet url). const send = (code: string) => // Decorate it with a spinner - spinnerModal(t('SendingToGoogleDrive'), + spinnerModal(t("Sending file to Google Drive"), pageModel.appModel.api.getDocAPI(doc.id) .sendToDrive(code, pageModel.currentDocTitle.get()) );