From fb8bd2ddcd5fa257e3a1e6b36c21b3d0d9604e25 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:23:59 +0100 Subject: [PATCH 01/17] Change translation keys for models directory --- app/client/models/AppModel.ts | 2 +- app/client/models/DocPageModel.ts | 14 +++++++------- app/client/models/UserManagerModel.ts | 26 +++++++++++++------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/client/models/AppModel.ts b/app/client/models/AppModel.ts index a71bff65..5a0b3849 100644 --- a/app/client/models/AppModel.ts +++ b/app/client/models/AppModel.ts @@ -199,7 +199,7 @@ export class TopAppModelImpl extends Disposable implements TopAppModel { if (org.billingAccount && org.billingAccount.product && org.billingAccount.product.name === 'suspended') { this.notifier.createUserMessage( - t('TeamSiteSuspended'), + t("This team site is suspended. Documents can be read, but not modified."), {actions: ['renew', 'personal']} ); } diff --git a/app/client/models/DocPageModel.ts b/app/client/models/DocPageModel.ts index affd7eb3..a9f4f15c 100644 --- a/app/client/models/DocPageModel.ts +++ b/app/client/models/DocPageModel.ts @@ -236,13 +236,13 @@ export class DocPageModelImpl extends Disposable implements DocPageModel { const isDenied = (err as any).code === 'ACL_DENY'; const isDocOwner = isOwner(this.currentDoc.get()); confirmModal( - t("ErrorAccessingDocument"), + t("Error accessing document"), t("Reload"), async () => window.location.reload(true), - isDocOwner ? t('ReloadingOrRecoveryMode', {error: err.message}) : + isDocOwner ? t("You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]", {error: err.message}) : t('AccessError', {context: isDenied ? 'denied' : 'recover', error: err.message}), { hideCancel: true, - extraButtons: (isDocOwner && !isDenied) ? bigBasicButton(t('EnterRecoveryMode'), dom.on('click', async () => { + extraButtons: (isDocOwner && !isDenied) ? bigBasicButton(t("Enter recovery mode"), dom.on('click', async () => { await this._api.getDocAPI(this.currentDocId.get()!).recover(true); window.location.reload(true); }), testId('modal-recovery-mode')) : null, @@ -338,18 +338,18 @@ function addMenu(importSources: ImportSource[], gristDoc: GristDoc, isReadonly: menuItem( (elem) => openPageWidgetPicker(elem, gristDoc, (val) => gristDoc.addNewPage(val).catch(reportError), {isNewPage: true, buttonLabel: 'Add Page'}), - menuIcon("Page"), t("AddPage"), testId('dp-add-new-page'), + menuIcon("Page"), t("Add Page"), testId('dp-add-new-page'), dom.cls('disabled', isReadonly) ), menuItem( (elem) => openPageWidgetPicker(elem, gristDoc, (val) => gristDoc.addWidgetToPage(val).catch(reportError), {isNewPage: false, selectBy}), - menuIcon("Widget"), t("AddWidgetToPage"), testId('dp-add-widget-to-page'), + menuIcon("Widget"), t("Add Widget to Page"), testId('dp-add-widget-to-page'), // disable for readonly doc and all special views dom.cls('disabled', (use) => typeof use(gristDoc.activeViewId) !== 'number' || isReadonly), ), menuItem(() => gristDoc.addEmptyTable().catch(reportError), - menuIcon("TypeTable"), t("AddEmptyTable"), testId('dp-empty-table'), + menuIcon("TypeTable"), t("Add Empty Table"), testId('dp-empty-table'), dom.cls('disabled', isReadonly) ), menuDivider(), @@ -361,7 +361,7 @@ function addMenu(importSources: ImportSource[], gristDoc: GristDoc, isReadonly: dom.cls('disabled', isReadonly) ) ), - isReadonly ? menuText(t('NoEditAccess')) : null, + isReadonly ? menuText(t("You do not have edit access to this document")) : null, testId('dp-add-new-menu') ]; } diff --git a/app/client/models/UserManagerModel.ts b/app/client/models/UserManagerModel.ts index 847f4c4f..9e648be8 100644 --- a/app/client/models/UserManagerModel.ts +++ b/app/client/models/UserManagerModel.ts @@ -101,28 +101,28 @@ interface IBuildMemberOptions { export class UserManagerModelImpl extends Disposable implements UserManagerModel { // Select options for each individual user's role dropdown. public readonly userSelectOptions: IMemberSelectOption[] = [ - { value: roles.OWNER, label: t('Owner') }, - { value: roles.EDITOR, label: t('Editor') }, - { value: roles.VIEWER, label: t('Viewer') } + { value: roles.OWNER, label: t("Owner") }, + { value: roles.EDITOR, label: t("Editor") }, + { value: roles.VIEWER, label: t("Viewer") } ]; // Select options for each individual user's role dropdown in the org. public readonly orgUserSelectOptions: IOrgMemberSelectOption[] = [ - { value: roles.OWNER, label: t('Owner') }, - { value: roles.EDITOR, label: t('Editor') }, - { value: roles.VIEWER, label: t('Viewer') }, - { value: roles.MEMBER, label: t('NoDefaultAccess') }, + { value: roles.OWNER, label: t("Owner") }, + { value: roles.EDITOR, label: t("Editor") }, + { value: roles.VIEWER, label: t("Viewer") }, + { value: roles.MEMBER, label: t("No Default Access") }, ]; // Select options for the resource's maxInheritedRole dropdown. public readonly inheritSelectOptions: IMemberSelectOption[] = [ - { value: roles.OWNER, label: t('InFull') }, - { value: roles.EDITOR, label: t('ViewAndEdit') }, - { value: roles.VIEWER, label: t('ViewOnly') }, - { value: null, label: t('None') } + { value: roles.OWNER, label: t("In Full") }, + { value: roles.EDITOR, label: t("View & Edit") }, + { value: roles.VIEWER, label: t("View Only") }, + { value: null, label: t("None") } ]; // Select options for the public member's role dropdown. public readonly publicUserSelectOptions: IMemberSelectOption[] = [ - { value: roles.EDITOR, label: t('Editor') }, - { value: roles.VIEWER, label: t('Viewer') }, + { value: roles.EDITOR, label: t("Editor") }, + { value: roles.VIEWER, label: t("Viewer") }, ]; public activeUser: FullUser|null = this._options.activeUser ?? null; From 156a471db7b04e5ed7984fdf02d932500640060c Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:24:54 +0100 Subject: [PATCH 02/17] Change translation keys for lib directory --- app/client/lib/ACUserManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/client/lib/ACUserManager.ts b/app/client/lib/ACUserManager.ts index 65f5e9e5..66cc095e 100644 --- a/app/client/lib/ACUserManager.ts +++ b/app/client/lib/ACUserManager.ts @@ -109,10 +109,10 @@ export function buildACMemberEmail( cssUserImagePlus.cls('-invalid', (use) => !use(enableAdd), )), cssMemberText( - cssMemberPrimaryPlus(t("InviteNewMember")), + cssMemberPrimaryPlus(t("Invite new member")), cssMemberSecondaryPlus( // dom.text(use => `We'll email an invite to ${use(emailObs)}`) - dom.text(use => t('InviteEmail', {email: use(emailObs)})) // TODO i18next + dom.text(use => t("We'll email an invite to {{email}}", {email: use(emailObs)})) // TODO i18next ) ), testId("um-add-email") @@ -143,7 +143,7 @@ export function buildACMemberEmail( (emailInput = cssEmailInput( emailObs, {onInput: true, isValid}, - {type: "email", placeholder: t("EmailInputPlaceholder")}, + {type: "email", placeholder: t("Enter email address")}, dom.on("input", acOpen), dom.on("focus", acOpen), dom.on("click", acOpen), From 32e3d25ae56abd255683bba214edd502454e23d5 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:31:42 +0100 Subject: [PATCH 03/17] Change translation keys for aclui directory --- app/client/aclui/AccessRules.ts | 45 ++++++++++++++------------- app/client/aclui/PermissionsWidget.ts | 6 ++-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index d892a4a8..7f49a2d2 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -329,21 +329,21 @@ export class AccessRules extends Disposable { bigBasicButton({disabled: true}, dom.hide(this._savingEnabled), dom.text((use) => { const s = use(this._ruleStatus); - return s === RuleStatus.CheckPending ? t('Checking') : - s === RuleStatus.Unchanged ? t('Saved') : t('Invalid'); + return s === RuleStatus.CheckPending ? t("Checking...") : + s === RuleStatus.Unchanged ? t("Saved") : t("Invalid"); }), testId('rules-non-save') ), - bigPrimaryButton(t('Save'), dom.show(this._savingEnabled), + bigPrimaryButton(t("Save"), dom.show(this._savingEnabled), dom.on('click', () => this.save()), testId('rules-save'), ), - bigBasicButton(t('Reset'), dom.show(use => use(this._ruleStatus) !== RuleStatus.Unchanged), + bigBasicButton(t("Reset"), dom.show(use => use(this._ruleStatus) !== RuleStatus.Unchanged), dom.on('click', () => this.update()), testId('rules-revert'), ), - bigBasicButton(t('AddTableRules'), cssDropdownIcon('Dropdown'), {style: 'margin-left: auto'}, + bigBasicButton(t("Add Table Rules"), cssDropdownIcon('Dropdown'), {style: 'margin-left: auto'}, menu(() => this.allTableIds.map((tableId) => // Add the table on a timeout, to avoid disabling the clicked menu item @@ -355,8 +355,8 @@ export class AccessRules extends Disposable { ), ), ), - bigBasicButton(t('AddUserAttributes'), dom.on('click', () => this._addUserAttributes())), - bigBasicButton(t('ViewAs'), cssDropdownIcon('Dropdown'), + bigBasicButton(t('Add User Attributes'), dom.on('click', () => this._addUserAttributes())), + bigBasicButton(t('View As'), cssDropdownIcon('Dropdown'), elem => this._aclUsersPopup.attachPopup(elem, {placement: 'bottom-end'}), dom.style('visibility', use => use(this._aclUsersPopup.isInitialized) ? '' : 'hidden')), ), @@ -375,15 +375,15 @@ export class AccessRules extends Disposable { shadowScroll( dom.maybe(use => use(this._userAttrRules).length, () => cssSection( - cssSectionHeading(t('UserAttributes')), + cssSectionHeading(t("User Attributes")), cssTableRounded( cssTableHeaderRow( cssCell1(cssCell.cls('-rborder'), cssCell.cls('-center'), cssColHeaderCell('Name')), cssCell4( cssColumnGroup( - cssCell1(cssColHeaderCell(t('AttributeToLookUp'))), - cssCell1(cssColHeaderCell(t('LookupTable'))), - cssCell1(cssColHeaderCell(t('LookupColumn'))), + cssCell1(cssColHeaderCell(t("Attribute to Look Up"))), + cssCell1(cssColHeaderCell(t("Lookup Table"))), + cssCell1(cssColHeaderCell(t("Lookup Column"))), cssCellIcon(), ), ), @@ -394,7 +394,7 @@ export class AccessRules extends Disposable { ), dom.forEach(this._tableRules, (tableRules) => tableRules.buildDom()), cssSection( - cssSectionHeading(t("DefaultRules"), testId('rule-table-header')), + cssSectionHeading(t("Default Rules"), testId('rule-table-header')), cssTableRounded( cssTableHeaderRow( cssCell1(cssCell.cls('-rborder'), cssCell.cls('-center'), cssColHeaderCell('Columns')), @@ -628,13 +628,13 @@ class TableRules extends Disposable { public buildDom() { return cssSection( cssSectionHeading( - dom('span', t('RulesForTable'), cssTableName(this._accessRules.getTableTitle(this.tableId))), + dom('span', t("Rules for table "), cssTableName(this._accessRules.getTableTitle(this.tableId))), cssIconButton(icon('Dots'), {style: 'margin-left: auto'}, menu(() => [ - menuItemAsync(() => this._addColumnRuleSet(), t('AddColumnRule')), - menuItemAsync(() => this._addDefaultRuleSet(), t('AddDefaultRule'), + menuItemAsync(() => this._addColumnRuleSet(), t("Add Column Rule")), + menuItemAsync(() => this._addDefaultRuleSet(), t("Add Default Rule"), dom.cls('disabled', use => Boolean(use(this._defaultRuleSet)))), - menuItemAsync(() => this.remove(), t('DeleteTableRules')), + menuItemAsync(() => this._accessRules.removeTableRules(this), t("Delete Table Rules")), ]), testId('rule-table-menu-btn'), ), @@ -762,7 +762,7 @@ class TableRules extends Disposable { class SpecialRules extends TableRules { public buildDom() { return cssSection( - cssSectionHeading(t('SpecialRules'), testId('rule-table-header')), + cssSectionHeading(t("Special Rules"), testId('rule-table-header')), this.buildColumnRuleSets(), this.buildErrors(), testId('rule-table'), @@ -1009,17 +1009,18 @@ class DefaultObsRuleSet extends ObsRuleSet { function getSpecialRuleDescription(type: string): string { switch (type) { case 'AccessRules': - return t('AccessRulesDescription'); + return t("Allow everyone to view Access Rules."); case 'FullCopies': - return t('FullCopiesDescription'); + return t(`Allow everyone to copy the entire document, or view it in full in fiddle mode. +Useful for examples and templates, but not for sensitive data.`); default: return type; } } function getSpecialRuleName(type: string): string { switch (type) { - case 'AccessRules': return t('AccessRulesName'); - case 'FullCopies': return t('FullCopies'); + case 'AccessRules': return t("Permission to view Access Rules"); + case 'FullCopies': return t("Permission to access the document in full when needed"); default: return type; } } @@ -1157,7 +1158,7 @@ class ObsUserAttributeRule extends Disposable { cssCell1(cssCell.cls('-rborder'), cssCellContent( cssInput(this._name, async (val) => this._name.set(val), - {placeholder: t('AttributeNamePlaceholder')}, + {placeholder: t("Attribute name")}, (this._options.focus ? (elem) => { setTimeout(() => elem.focus(), 0); } : null), testId('rule-userattr-name'), ), diff --git a/app/client/aclui/PermissionsWidget.ts b/app/client/aclui/PermissionsWidget.ts index 052a8be6..e121302f 100644 --- a/app/client/aclui/PermissionsWidget.ts +++ b/app/client/aclui/PermissionsWidget.ts @@ -64,13 +64,13 @@ export function permissionsWidget( null ), // If the set matches any recognized pattern, mark that item with a tick (checkmark). - cssMenuItem(() => setPermissions(allowAll), tick(isEqual(pset.get(), allowAll)), t('AllowAll'), + cssMenuItem(() => setPermissions(allowAll), tick(isEqual(pset.get(), allowAll)), t("Allow All"), dom.cls('disabled', options.disabled) ), - cssMenuItem(() => setPermissions(denyAll), tick(isEqual(pset.get(), denyAll)), t('DenyAll'), + cssMenuItem(() => setPermissions(denyAll), tick(isEqual(pset.get(), denyAll)), t("Deny All"), dom.cls('disabled', options.disabled) ), - cssMenuItem(() => setPermissions(readOnly), tick(isEqual(pset.get(), readOnly)), t('ReadOnly'), + cssMenuItem(() => setPermissions(readOnly), tick(isEqual(pset.get(), readOnly)), t("Read Only"), dom.cls('disabled', options.disabled) ), cssMenuItem(() => setPermissions(empty), From 7a2a0a797fc0b32f33052d539308c5c6ea35ed94 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:35:10 +0100 Subject: [PATCH 04/17] Change translation keys for ui2018 directory --- app/client/ui2018/ColorSelect.ts | 6 +++--- app/client/ui2018/breadcrumbs.ts | 9 +++++---- app/client/ui2018/menus.ts | 6 +++--- app/client/ui2018/modals.ts | 6 +++--- app/client/ui2018/pages.ts | 6 +++--- app/client/ui2018/search.ts | 8 ++++---- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/client/ui2018/ColorSelect.ts b/app/client/ui2018/ColorSelect.ts index 22caaa8c..b6d41d97 100644 --- a/app/client/ui2018/ColorSelect.ts +++ b/app/client/ui2018/ColorSelect.ts @@ -64,7 +64,7 @@ export function colorSelect( onSave, onOpen, onRevert, - placeholder = t('DefaultCellStyle'), + placeholder = t("Default cell style"), } = options; const selectBtn = cssSelectBtn( cssContent( @@ -188,12 +188,12 @@ function buildColorPicker(ctl: IOpenController, }), cssButtonRow( - primaryButton(t('Apply'), + primaryButton(t("Apply"), dom.on('click', () => ctl.close()), dom.boolAttr("disabled", notChanged), testId('colors-save') ), - basicButton(t('Cancel'), + basicButton(t("Cancel"), dom.on('click', () => revert()), testId('colors-cancel') ) diff --git a/app/client/ui2018/breadcrumbs.ts b/app/client/ui2018/breadcrumbs.ts index d89b62c3..cb13f67a 100644 --- a/app/client/ui2018/breadcrumbs.ts +++ b/app/client/ui2018/breadcrumbs.ts @@ -138,19 +138,20 @@ export function docBreadcrumbs( dom.maybe(options.isPublic, () => cssPublicIcon('PublicFilled', testId('bc-is-public'))), dom.domComputed((use) => { if (options.isSnapshot && use(options.isSnapshot)) { - return cssTag(t('Snapshot'), testId('snapshot-tag')); + return cssTag(t("snapshot"), testId('snapshot-tag')); } if (use(options.isFork)) { - return cssTag(t('Unsaved'), testId('unsaved-tag')); + return cssTag(t("unsaved"), testId('unsaved-tag')); } if (use(options.isRecoveryMode)) { - return cssAlertTag(t('RecoveryMode'), + return cssAlertTag(t("recovery mode"), dom('a', dom.on('click', () => options.cancelRecoveryMode()), icon('CrossSmall')), testId('recovery-mode-tag')); } if (use(options.isFiddle)) { - return cssTag(t('Fiddle'), tooltip({title: t('FiddleExplanation')}), testId('fiddle-tag')); + return cssTag(t("fiddle"), tooltip({title: t(`You may make edits, but they will create a new copy and will +not affect the original document.`)}), testId('fiddle-tag')); } }), separator(' / ', diff --git a/app/client/ui2018/menus.ts b/app/client/ui2018/menus.ts index a371c2bd..e8a19dab 100644 --- a/app/client/ui2018/menus.ts +++ b/app/client/ui2018/menus.ts @@ -189,7 +189,7 @@ export function multiSelect(selectedOptions: MutableObsArray, const selectedOptionsText = Computed.create(null, selectedOptionsSet, (use, selectedOpts) => { if (selectedOpts.size === 0) { - return options.placeholder ?? t('SelectFields'); + return options.placeholder ?? t("Select fields"); } const optionArray = Array.isArray(availableOptions) ? availableOptions : use(availableOptions); @@ -323,8 +323,8 @@ export function upgradableMenuItem(needUpgrade: boolean, action: () => void, ... export function upgradeText(needUpgrade: boolean, onClick: () => void) { if (!needUpgrade) { return null; } - return menuText(dom('span', t('WorkspacesAvailableOnTeamPlans'), - cssUpgradeTextButton(t('UpgradeNow'), dom.on('click', () => onClick())))); + return menuText(dom('span', t("* Workspaces are available on team plans. "), + cssUpgradeTextButton(t("Upgrade now"), dom.on('click', () => onClick())))); } /** diff --git a/app/client/ui2018/modals.ts b/app/client/ui2018/modals.ts index 5e67cf59..a126d867 100644 --- a/app/client/ui2018/modals.ts +++ b/app/client/ui2018/modals.ts @@ -306,13 +306,13 @@ export function saveModal(createFunc: (ctl: IModalControl, owner: MultiHolder) = cssModalTitle(options.title, testId('modal-title')), cssModalBody(options.body), cssModalButtons( - bigPrimaryButton(options.saveLabel || t('Save'), + bigPrimaryButton(options.saveLabel || t("Save"), dom.boolAttr('disabled', isSaveDisabled), dom.on('click', save), testId('modal-confirm'), ), options.extraButtons, - options.hideCancel ? null : bigBasicButton(t('Cancel'), + options.hideCancel ? null : bigBasicButton(t("Cancel"), dom.on('click', () => ctl.close()), testId('modal-cancel'), ), @@ -426,7 +426,7 @@ export function invokePrompt( const prom = new Promise((resolve) => { onResolve = resolve; }); - promptModal(title, onResolve!, btnText ?? t('Ok'), initial, placeholder, () => { + promptModal(title, onResolve!, btnText ?? t("Ok"), initial, placeholder, () => { if (onResolve) { onResolve(undefined); } diff --git a/app/client/ui2018/pages.ts b/app/client/ui2018/pages.ts index 9c0a4ed4..2bae3e61 100644 --- a/app/client/ui2018/pages.ts +++ b/app/client/ui2018/pages.ts @@ -37,11 +37,11 @@ export function buildPageDom(name: Observable, actions: PageActions, ... const pageMenu = () => [ menuItem(() => isRenaming.set(true), t("Rename"), testId('rename'), dom.cls('disabled', actions.isReadonly)), - menuItem(actions.onRemove, t('Remove'), testId('remove'), + menuItem(actions.onRemove, t("Remove"), testId('remove'), dom.cls('disabled', (use) => use(actions.isReadonly) || actions.isRemoveDisabled())), - menuItem(actions.onDuplicate, t('DuplicatePage'), testId('duplicate'), + menuItem(actions.onDuplicate, t("Duplicate Page"), testId('duplicate'), dom.cls('disabled', actions.isReadonly)), - dom.maybe(actions.isReadonly, () => menuText(t('NoEditAccess'))), + dom.maybe(actions.isReadonly, () => menuText(t("You do not have edit access to this document"))), ]; let pageElem: HTMLElement; diff --git a/app/client/ui2018/search.ts b/app/client/ui2018/search.ts index ade4acef..f2d9f1f6 100644 --- a/app/client/ui2018/search.ts +++ b/app/client/ui2018/search.ts @@ -146,7 +146,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) { model.isOpen.set(_value === undefined ? !model.isOpen.get() : _value); }, 100); const inputElem: HTMLInputElement = searchInput(model.value, {onInput: true}, - {type: 'text', placeholder: t('SearchInDocument')}, + {type: 'text', placeholder: t("Search in document")}, dom.on('blur', () => ( keepExpanded ? setTimeout(() => inputElem.focus(), 0) : @@ -187,7 +187,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) { const noMatch = use(model.noMatch); const isEmpty = use(model.isEmpty); if (isEmpty) { return null; } - if (noMatch) { return cssLabel(t("NoResults")); } + if (noMatch) { return cssLabel(t("No results")); } return [ cssArrowBtn( icon('Dropdown'), @@ -197,7 +197,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) { dom.on('click', () => model.findNext()), hoverTooltip( [ - t('FindNext'), + t("Find Next "), cssShortcut(`(${['Enter', allCommands.findNext.humanKeys].join(', ')})`), ], {key: 'searchArrowBtnTooltip'} @@ -211,7 +211,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) { dom.on('click', () => model.findPrev()), hoverTooltip( [ - t('FindPrevious'), + t("Find Previous "), cssShortcut(allCommands.findPrev.getKeysDesc()), ], {key: 'searchArrowBtnTooltip'} From 24a656406e489b94d61c93580f0bb5149833b9bd Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:40:02 +0100 Subject: [PATCH 05/17] Change translation keys for components directory --- app/client/components/ActionLog.ts | 8 ++++---- app/client/components/ChartView.ts | 12 ++++++------ app/client/components/CodeEditorPanel.ts | 4 ++-- app/client/components/DataTables.ts | 12 ++++++------ app/client/components/DocumentUsage.ts | 16 ++++++++-------- app/client/components/Drafts.ts | 6 +++--- app/client/components/GristDoc.ts | 6 +++--- app/client/components/Importer.ts | 6 +++--- app/client/components/PluginScreen.ts | 2 +- app/client/components/RecordLayout.js | 2 +- app/client/components/RefSelect.ts | 4 ++-- app/client/components/SelectionSummary.ts | 2 +- app/client/components/ValidationPanel.js | 4 ++-- app/client/components/ViewConfigTab.js | 18 +++++++++--------- app/client/components/duplicatePage.ts | 4 ++-- 15 files changed, 53 insertions(+), 53 deletions(-) diff --git a/app/client/components/ActionLog.ts b/app/client/components/ActionLog.ts index b6194003..4ea80f3e 100644 --- a/app/client/components/ActionLog.ts +++ b/app/client/components/ActionLog.ts @@ -227,7 +227,7 @@ export class ActionLog extends dispose.Disposable implements IDomComponent { } private _buildLogDom() { - this._loadActionSummaries().catch((error) => gristNotify(t("ActionLogFailed"))); + this._loadActionSummaries().catch((error) => gristNotify(t("Action Log failed to load"))); return dom('div.action_log', dom('div.preference_item', koForm.checkbox(this._showAllTables, @@ -395,7 +395,7 @@ export class ActionLog extends dispose.Disposable implements IDomComponent { const newName = tableRename[1]; if (!newName) { // TODO - find a better way to send informative notifications. - gristNotify(t('TableRemovedInAction', {tableId:tableId, actionNum: action.actionNum})); + gristNotify(t("Table {{tableId}} was subsequently removed in action #{{actionNum}}", {tableId:tableId, actionNum: action.actionNum})); return; } tableId = newName; @@ -406,7 +406,7 @@ export class ActionLog extends dispose.Disposable implements IDomComponent { // Check is this row was removed - if so there's no reason to go on. if (td.removeRows.indexOf(rowId) >= 0) { // TODO - find a better way to send informative notifications. - gristNotify(t("RowRemovedInAction", {actionNum})); + gristNotify(t("This row was subsequently removed in action {{action.actionNum}}", {actionNum})); return; } @@ -416,7 +416,7 @@ export class ActionLog extends dispose.Disposable implements IDomComponent { const newName = columnRename[1]; if (!newName) { // TODO - find a better way to send informative notifications. - gristNotify(t("ColumnRemovedInAction", {colId, actionNum: action.actionNum})); + gristNotify(t("Column {{colId}} was subsequently removed in action #{{action.actionNum}}", {colId, actionNum: action.actionNum})); return; } colId = newName; diff --git a/app/client/components/ChartView.ts b/app/client/components/ChartView.ts index aa42ed84..1d267ce9 100644 --- a/app/client/components/ChartView.ts +++ b/app/client/components/ChartView.ts @@ -655,8 +655,8 @@ export class ChartConfig extends GrainJSDisposable { testId('error-bars'), ), dom.domComputed(this._optionsObj.prop('errorBars'), (value: ChartOptions["errorBars"]) => - value === 'symmetric' ? cssRowHelp(t('EachYFollowedByOne')) : - value === 'separate' ? cssRowHelp(t('EachYFollowedByTwo')) : + value === 'symmetric' ? cssRowHelp(t("Each Y series is followed by a series for the length of error bars.")) : + value === 'separate' ? cssRowHelp(t("Each Y series is followed by two series, for top and bottom error bars.")) : null ), ]), @@ -669,7 +669,7 @@ export class ChartConfig extends GrainJSDisposable { select(this._groupDataColId, this._groupDataOptions), testId('group-by-column'), ), - cssHintRow(t('CreateSeparateSeries')), + cssHintRow(t("Create separate series for each value of the selected column.")), ]), // TODO: user should select x axis before widget reach page @@ -677,7 +677,7 @@ export class ChartConfig extends GrainJSDisposable { cssRow( select( this._xAxis, this._columnsOptions, - { defaultLabel: t('PickColumn') } + { defaultLabel: t("Pick a column") } ), testId('x-axis'), ), @@ -773,7 +773,7 @@ export class ChartConfig extends GrainJSDisposable { private async _setGroupDataColumn(colId: string) { const viewFields = this._section.viewFields.peek().peek(); - await this._gristDoc.docData.bundleActions(t('SelectedNewGroupDataColumns'), async () => { + await this._gristDoc.docData.bundleActions(t("selected new group data columns"), async () => { this._freezeXAxis.set(true); this._freezeYAxis.set(true); try { @@ -872,7 +872,7 @@ export class ChartConfig extends GrainJSDisposable { private async _setAggregation(val: boolean) { try { this._freezeXAxis.set(true); - await this._gristDoc.docData.bundleActions(t("ToggleChartAggregation"), async () => { + await this._gristDoc.docData.bundleActions(t("Toggle chart aggregation"), async () => { if (val) { await this._doAggregation(); } else { diff --git a/app/client/components/CodeEditorPanel.ts b/app/client/components/CodeEditorPanel.ts index a1d3af58..99363842 100644 --- a/app/client/components/CodeEditorPanel.ts +++ b/app/client/components/CodeEditorPanel.ts @@ -28,8 +28,8 @@ export class CodeEditorPanel extends DisposableWithEvents { return dom('div.g-code-panel.clipboard', {tabIndex: "-1"}, dom.maybe(this._denied, () => dom('div.g-code-panel-denied', - dom('h2', dom.text(t('AccessDenied'))), - dom('div', dom.text(t('CodeViewOnlyFullAccess'))), + dom('h2', dom.text(t("Access denied"))), + dom('div', dom.text(t("Code View is available only when you have full document access."))), )), dom.maybe(this._schema, (schema) => { // The reason to scope and rebuild instead of using `kd.text(schema)` is because diff --git a/app/client/components/DataTables.ts b/app/client/components/DataTables.ts index 3ea5f37d..06bcd75a 100644 --- a/app/client/components/DataTables.ts +++ b/app/client/components/DataTables.ts @@ -45,7 +45,7 @@ export class DataTables extends Disposable { cssTableList( /*************** List section **********/ testId('list'), - cssHeader(t('RawDataTables')), + cssHeader(t("Raw Data Tables")), cssList( dom.forEach(this._tables, tableRec => cssItem( @@ -65,11 +65,11 @@ export class DataTables extends Disposable { testId('table-id'), dom.text(tableRec.tableId), ), - { title : t('ClickToCopy') }, + { title : t("Click to copy") }, dom.on('click', async (e, d) => { e.stopImmediatePropagation(); e.preventDefault(); - showTransientTooltip(d, t('TableIDCopied'), { + showTransientTooltip(d, t("Table ID copied to clipboard"), { key: 'copy-table-id' }); await copyToClipboard(tableRec.tableId.peek()); @@ -127,7 +127,7 @@ export class DataTables extends Disposable { return [ menuItem( () => this._duplicateTable(table), - t('DuplicateTable'), + t("Duplicate Table"), testId('menu-duplicate-table'), dom.cls('disabled', use => use(isReadonly) || @@ -144,7 +144,7 @@ export class DataTables extends Disposable { use(docModel.visibleTables.getObservable()).length <= 1 && !use(table.isHidden) )) ), - dom.maybe(isReadonly, () => menuText(t("NoEditAccess"))), + dom.maybe(isReadonly, () => menuText(t("You do not have edit access to this document"))), ]; } @@ -160,7 +160,7 @@ export class DataTables extends Disposable { function doRemove() { return docModel.docData.sendAction(['RemoveTable', r.tableId()]); } - confirmModal(t("DeleteData", {formattedTableName : r.formattedTableName()}), 'Delete', doRemove); + confirmModal(t("Delete {{formattedTableName}} data, and remove it from all pages?", {formattedTableName : r.formattedTableName()}), 'Delete', doRemove); } private _tableRows(table: TableRec) { diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 83bcbc23..914ef0ba 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -60,7 +60,7 @@ export class DocumentUsage extends Disposable { // Invalid row limits are currently treated as if they are undefined. const maxValue = maxRows && maxRows > 0 ? maxRows : undefined; return { - name: t('Rows'), + name: t("Rows"), currentValue: typeof rowCount !== 'object' ? undefined : rowCount.total, maximumValue: maxValue ?? DEFAULT_MAX_ROWS, unit: 'rows', @@ -75,7 +75,7 @@ export class DocumentUsage extends Disposable { // Invalid data size limits are currently treated as if they are undefined. const maxValue = maxSize && maxSize > 0 ? maxSize : undefined; return { - name: t('DataSize'), + name: t("Data Size"), currentValue: typeof dataSize !== 'number' ? undefined : dataSize, maximumValue: maxValue ?? DEFAULT_MAX_DATA_SIZE, unit: 'MB', @@ -97,7 +97,7 @@ export class DocumentUsage extends Disposable { // Invalid attachments size limits are currently treated as if they are undefined. const maxValue = maxSize && maxSize > 0 ? maxSize : undefined; return { - name: t('AttachmentsSize'), + name: t("Attachments Size"), currentValue: typeof attachmentsSize !== 'number' ? undefined : attachmentsSize, maximumValue: maxValue ?? DEFAULT_MAX_ATTACHMENTS_SIZE, unit: 'GB', @@ -135,7 +135,7 @@ export class DocumentUsage extends Disposable { public buildDom() { return dom('div', - cssHeader(t('Usage'), testId('heading')), + cssHeader(t("Usage"), testId('heading')), dom.domComputed(this._areAllMetricsPending, (isLoading) => { if (isLoading) { return cssSpinner(loadingSpinner(), testId('loading')); } @@ -149,7 +149,7 @@ export class DocumentUsage extends Disposable { return dom.domComputed((use) => { const isAccessDenied = use(this._isAccessDenied); if (isAccessDenied === null) { return null; } - if (isAccessDenied) { return buildMessage(t('UsageStatisticsOnlyFullAccess')); } + if (isAccessDenied) { return buildMessage(t("Usage statistics are only available to users with full access to the document data.")); } const org = use(this._currentOrg); const product = use(this._currentProduct); @@ -237,12 +237,12 @@ export function buildUpgradeMessage( variant: 'short' | 'long', onUpgrade: () => void, ) { - if (!canUpgrade) { return t('LimitContactSiteOwner'); } + if (!canUpgrade) { return t("Contact the site owner to upgrade the plan to raise limits."); } - const upgradeLinkText = t('UpgradeLinkText') + const upgradeLinkText = t("start your 30-day free trial of the Pro plan.") // TODO i18next return [ - variant === 'short' ? null : t('ForHigherLimits'), + variant === 'short' ? null : t("For higher limits, "), buildUpgradeLink( variant === 'short' ? capitalizeFirstWord(upgradeLinkText) : upgradeLinkText, () => onUpgrade(), diff --git a/app/client/components/Drafts.ts b/app/client/components/Drafts.ts index da21b7a6..64316a68 100644 --- a/app/client/components/Drafts.ts +++ b/app/client/components/Drafts.ts @@ -273,7 +273,7 @@ class NotificationAdapter extends Disposable implements Notification { } public showUndoDiscard() { const notifier = this._doc.app.topAppModel.notifier; - const notification = notifier.createUserMessage(t("UndoDiscard"), { + const notification = notifier.createUserMessage(t("Undo discard"), { message: () => discardNotification( dom.on("click", () => { @@ -421,7 +421,7 @@ const styledTooltip = styled('div', ` function cellTooltip(clb: () => any) { return function (ctl: ITooltipControl) { return styledTooltip( - cssLink(t('RestoreLastEdit'), + cssLink(t("Restore last edit"), dom.on('mousedown', (ev) => { ev.preventDefault(); ctl.close(); clb(); }), testId('draft-tooltip'), ), @@ -440,7 +440,7 @@ const styledNotification = styled('div', ` `); function discardNotification(...args: IDomArgs>) { return styledNotification( - t("UndoDiscard"), + t("Undo discard"), testId("draft-notification"), ...args ); diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index 059a1c25..c5c8f46e 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -314,7 +314,7 @@ export class GristDoc extends DisposableWithEvents { const importSourceElems = ImportSourceElement.fromArray(this.docPluginManager.pluginsList); const importMenuItems = [ { - label: t('ImportFromFile'), + label: t("Import from file"), action: () => Importer.selectAndImport(this, importSourceElems, null, createPreview), }, ...importSourceElems.map(importSourceElem => ({ @@ -599,7 +599,7 @@ export class GristDoc extends DisposableWithEvents { } } const res = await docData.bundleActions( - t("AddedNewLinkedSection", {viewName}), + t("Added new linked section to view {{viewName}}", {viewName}), () => this.addWidgetToPageImpl(val, tableId ?? null) ); @@ -680,7 +680,7 @@ export class GristDoc extends DisposableWithEvents { } return await this._viewLayout!.freezeUntil(docData.bundleActions( - t("SavedLinkedSectionIn", {title:section.title(), name: viewModel.name()}), + t("Saved linked section {{title}} in view {{name}}", {title:section.title(), name: viewModel.name()}), async () => { // if table changes or a table is made a summary table, let's replace the view section by a diff --git a/app/client/components/Importer.ts b/app/client/components/Importer.ts index 248ea324..c9fdf8e9 100644 --- a/app/client/components/Importer.ts +++ b/app/client/components/Importer.ts @@ -631,7 +631,7 @@ export class Importer extends DisposableWithEvents { cssMergeOptions( cssMergeOptionsToggle(labeledSquareCheckbox( updateExistingRecords, - t('UpdateExistingRecords'), + t("Update existing records"), dom.autoDispose(updateRecordsListener), testId('importer-update-existing-records') )), @@ -646,14 +646,14 @@ export class Importer extends DisposableWithEvents { return [ cssMergeOptionsMessage( - t('MergeRowsThatMatch'), + t("Merge rows that match these fields:"), testId('importer-merge-fields-message') ), multiSelect( mergeCols, section.viewFields().peek().map(f => ({label: f.label(), value: f.colId()})) ?? [], { - placeholder: t("SelectFieldsToMatch"), + placeholder: t("Select fields to match on"), error: hasInvalidMergeCols }, dom.autoDispose(mergeColsListener), diff --git a/app/client/components/PluginScreen.ts b/app/client/components/PluginScreen.ts index 923c19b0..3eb9cb88 100644 --- a/app/client/components/PluginScreen.ts +++ b/app/client/components/PluginScreen.ts @@ -55,7 +55,7 @@ export class PluginScreen extends Disposable { public renderError(message: string) { this.render([ this._buildModalTitle(), - cssModalBody(t('ImportFailed'), message, testId('importer-error')), + cssModalBody(t("Import failed: "), message, testId('importer-error')), cssModalButtons( bigBasicButton('Close', dom.on('click', () => this.close()), diff --git a/app/client/components/RecordLayout.js b/app/client/components/RecordLayout.js index 1193ad28..dfa48e3a 100644 --- a/app/client/components/RecordLayout.js +++ b/app/client/components/RecordLayout.js @@ -263,7 +263,7 @@ RecordLayout.prototype.saveLayoutSpec = async function(layoutSpec) { // Use separate copies of addColAction, since sendTableActions modified each in-place. let addActions = gutil.arrayRepeat(addColNum, 0).map(() => addColAction.slice()); - await docData.bundleActions(t('UpdatingRecordLayout'), () => { + await docData.bundleActions(t("Updating record layout."), () => { return Promise.try(() => { return addColNum > 0 ? docModel.dataTables[tableId].sendTableActions(addActions) : []; }) diff --git a/app/client/components/RefSelect.ts b/app/client/components/RefSelect.ts index e3c7d15d..e45377aa 100644 --- a/app/client/components/RefSelect.ts +++ b/app/client/components/RefSelect.ts @@ -97,7 +97,7 @@ export class RefSelect extends Disposable { testId('ref-select-item'), ) ), - cssAddLink(cssAddIcon('Plus'), t('AddColumn'), + cssAddLink(cssAddIcon('Plus'), t("Add Column"), menu(() => [ ...this._validCols.peek() .filter((col) => !this._addedSet.peek().has(col.colId.peek())) @@ -105,7 +105,7 @@ export class RefSelect extends Disposable { menuItem(() => this._addFormulaField({ label: col.label(), value: col.colId() }), col.label.peek()) ), - cssEmptyMenuText(t("NoColumnsAdd")), + cssEmptyMenuText(t("No columns to add")), testId('ref-select-menu'), ]), testId('ref-select-add'), diff --git a/app/client/components/SelectionSummary.ts b/app/client/components/SelectionSummary.ts index 559c56d8..1e3d2bbe 100644 --- a/app/client/components/SelectionSummary.ts +++ b/app/client/components/SelectionSummary.ts @@ -266,7 +266,7 @@ export class SelectionSummary extends Disposable { async function doCopy(value: string, elem: Element) { await copyToClipboard(value); - showTransientTooltip(elem, t('CopiedClipboard'), {key: 'copy-selection-summary'}); + showTransientTooltip(elem, t("Copied to clipboard"), {key: 'copy-selection-summary'}); } const cssSummary = styled('div', ` diff --git a/app/client/components/ValidationPanel.js b/app/client/components/ValidationPanel.js index 530b9a2c..e9f155a7 100644 --- a/app/client/components/ValidationPanel.js +++ b/app/client/components/ValidationPanel.js @@ -33,7 +33,7 @@ dispose.makeDisposable(ValidationPanel); ValidationPanel.prototype.onAddRule = function() { this.validationsTable.sendTableAction(["AddRecord", null, { tableRef: this.docTables.at(0).id(), - name: t("RuleLength", {length: this.validations.peekLength + 1}), + name: t("Rule {{length}}", {length: this.validations.peekLength + 1}), formula: "" }]) .then(function() { @@ -86,7 +86,7 @@ ValidationPanel.prototype.buildDom = function() { 2, '', 1, kf.buttonGroup( kf.button(() => editor.writeObservable(), - 'Apply', { title: t('UpdateFormula')}, + 'Apply', { title: t("Update formula (Shift+Enter)")}, kd.toggleClass('disabled', editorUpToDate) ) ) diff --git a/app/client/components/ViewConfigTab.js b/app/client/components/ViewConfigTab.js index 68b94b2b..6510f11d 100644 --- a/app/client/components/ViewConfigTab.js +++ b/app/client/components/ViewConfigTab.js @@ -124,9 +124,9 @@ ViewConfigTab.prototype._buildAdvancedSettingsDom = function() { const table = sectionData.section.table(); const isCollapsed = ko.observable(true); return [ - kf.collapserLabel(isCollapsed, t('AdvancedSettings'), dom.testId('ViewConfig_advanced')), + kf.collapserLabel(isCollapsed, t("Advanced settings"), dom.testId('ViewConfig_advanced')), kf.helpRow(kd.hide(isCollapsed), - t('BigTablesMayBeMarked'), + t("Big tables may be marked as \"on-demand\" to avoid loading them into the data engine."), kd.style('text-align', 'left'), kd.style('margin-top', '1.5rem') ), @@ -135,7 +135,7 @@ ViewConfigTab.prototype._buildAdvancedSettingsDom = function() { ), kf.row(kd.hide(isCollapsed), kf.buttonGroup(kf.button(() => this._makeOnDemand(table), - kd.text(() => table.onDemand() ? t('UnmarkOnDemandButton') : t('MakeOnDemandButton')), + kd.text(() => table.onDemand() ? t("Unmark On-Demand") : t("Make On-Demand")), dom.testId('ViewConfig_onDemandBtn') )) ), @@ -152,9 +152,9 @@ ViewConfigTab.prototype._buildThemeDom = function() { return cssRow( dom.autoDispose(theme), select(theme, [ - {label: t('Form'), value: 'form' }, - {label: t('Compact'), value: 'compact'}, - {label: t('Blocks'), value: 'blocks' }, + {label: t("Form"), value: 'form' }, + {label: t("Compact"), value: 'compact'}, + {label: t("Blocks"), value: 'blocks' }, ]), testId('detail-theme') ); @@ -173,7 +173,7 @@ ViewConfigTab.prototype._buildLayoutDom = function() { const layoutEditorObs = ko.computed(() => view && view.recordLayout && view.recordLayout.layoutEditor()); return cssRow({style: 'margin-top: 16px;'}, kd.maybe(layoutEditorObs, (editor) => editor.buildFinishButtons()), - primaryButton(t('EditCardLayout'), + primaryButton(t("Edit Card Layout"), dom.autoDispose(layoutEditorObs), dom.on('click', () => commands.allCommands.editLayout.run()), grainjsDom.hide(layoutEditorObs), @@ -222,8 +222,8 @@ ViewConfigTab.prototype._buildCustomTypeItems = function() { // 3) showObs: () => activeSection().customDef.mode() === "plugin", buildDom: () => kd.scope(activeSection, ({customDef}) => dom('div', - kf.row(5, t("PluginColon"), 13, kf.text(customDef.pluginId, {}, {list: "list_plugin"}, dom.testId('ViewConfigTab_customView_pluginId'))), - kf.row(5, t("SectionColon"), 13, kf.text(customDef.sectionId, {}, {list: "list_section"}, dom.testId('ViewConfigTab_customView_sectionId'))), + kf.row(5, t("Plugin: "), 13, kf.text(customDef.pluginId, {}, {list: "list_plugin"}, dom.testId('ViewConfigTab_customView_pluginId'))), + kf.row(5, t("Section: "), 13, kf.text(customDef.sectionId, {}, {list: "list_section"}, dom.testId('ViewConfigTab_customView_sectionId'))), // For both `customPlugin` and `selectedSection` it is possible for the value not to be in the // list of options. Combining and allows both to freely edit the value with // keyboard and to select it from a list. Although the content of the list seems to be diff --git a/app/client/components/duplicatePage.ts b/app/client/components/duplicatePage.ts index 3db955c6..a4e7bca9 100644 --- a/app/client/components/duplicatePage.ts +++ b/app/client/components/duplicatePage.ts @@ -30,7 +30,7 @@ export async function duplicatePage(gristDoc: GristDoc, pageId: number) { cssLabel("Name"), inputEl = cssInput({value: pageName + ' (copy)'}), ), - t("DoesNotCopyData"), + t("Note that this does not copy data, but creates another view of the same data."), ]) )); } @@ -41,7 +41,7 @@ async function makeDuplicate(gristDoc: GristDoc, pageId: number, pageName: strin const viewSections = sourceView.viewSections.peek().peek(); let viewRef = 0; await gristDoc.docData.bundleActions( - t("DuplicatePageName", {pageName}), + t("Duplicate page {{pageName}}", {pageName}), async () => { // create new view and new sections const results = await createNewViewSections(gristDoc.docData, viewSections); From b76fe50bf98f24dfeb9692245c02c90e1a38b539 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 14:57:29 +0100 Subject: [PATCH 06/17] Change translation keys for ui directory --- app/client/ui/AccountPage.ts | 30 +++++------ app/client/ui/AccountWidget.ts | 20 ++++---- app/client/ui/AddNewButton.ts | 2 +- app/client/ui/ApiKey.ts | 12 ++--- app/client/ui/App.ts | 6 +-- app/client/ui/AppHeader.ts | 6 +-- app/client/ui/CellContextMenu.ts | 16 +++--- app/client/ui/CustomSectionConfig.ts | 26 +++++----- app/client/ui/DocHistory.ts | 16 +++--- app/client/ui/DocMenu.ts | 76 ++++++++++++++-------------- app/client/ui/DocTour.ts | 4 +- app/client/ui/DocumentSettings.ts | 16 +++--- app/client/ui/DuplicateTable.ts | 8 +-- app/client/ui/FieldConfig.ts | 20 ++++---- app/client/ui/FilterConfig.ts | 2 +- app/client/ui/GridOptions.ts | 8 +-- app/client/ui/GridViewMenus.ts | 26 +++++----- app/client/ui/HomeIntro.ts | 42 +++++++-------- app/client/ui/HomeLeftPane.ts | 18 +++---- app/client/ui/LeftPanelCommon.ts | 2 +- app/client/ui/MakeCopyMenu.ts | 42 +++++++-------- app/client/ui/NotifyUI.ts | 20 ++++---- app/client/ui/OnBoardingPopups.ts | 2 +- app/client/ui/OpenVideoTour.ts | 6 +-- app/client/ui/PageWidgetPicker.ts | 10 ++-- app/client/ui/Pages.ts | 4 +- app/client/ui/RightPanel.ts | 44 ++++++++-------- app/client/ui/RowContextMenu.ts | 10 ++-- app/client/ui/ShareMenu.ts | 42 +++++++-------- app/client/ui/SiteSwitcher.ts | 4 +- app/client/ui/SortConfig.ts | 10 ++-- app/client/ui/ThemeConfig.ts | 4 +- app/client/ui/Tools.ts | 18 +++---- app/client/ui/TopBar.ts | 2 +- app/client/ui/TriggerFormulas.ts | 16 +++--- app/client/ui/ViewLayoutMenu.ts | 24 ++++----- app/client/ui/ViewSectionMenu.ts | 18 +++---- app/client/ui/VisibleFieldsConfig.ts | 12 ++--- app/client/ui/WelcomeQuestions.ts | 8 +-- app/client/ui/WidgetTitle.ts | 12 ++--- app/client/ui/errorPages.ts | 42 +++++++-------- app/client/ui/sendToDrive.ts | 2 +- 42 files changed, 354 insertions(+), 354 deletions(-) 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()) ); From 37dc0cc9fb09bdf4d649e723b1bfdd1b1a555b27 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 16:36:14 +0100 Subject: [PATCH 07/17] Change translation keys for simple context keys --- app/client/models/DocPageModel.ts | 3 ++- app/client/ui/ExampleInfo.ts | 24 ++++++++++++------------ app/client/ui/FieldConfig.ts | 8 ++++---- app/client/ui/FieldMenus.ts | 8 ++++---- app/client/ui/errorPages.ts | 4 ++-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/app/client/models/DocPageModel.ts b/app/client/models/DocPageModel.ts index a9f4f15c..6db13d20 100644 --- a/app/client/models/DocPageModel.ts +++ b/app/client/models/DocPageModel.ts @@ -240,7 +240,8 @@ export class DocPageModelImpl extends Disposable implements DocPageModel { t("Reload"), async () => window.location.reload(true), isDocOwner ? t("You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]", {error: err.message}) : - t('AccessError', {context: isDenied ? 'denied' : 'recover', error: err.message}), + isDenied ? t('Sorry, access to this document has been denied. [{{error}}]', {error: err.message}) : + t("Document owners can attempt to recover the document. [{{error}}]", {error: err.message}), { hideCancel: true, extraButtons: (isDocOwner && !isDenied) ? bigBasicButton(t("Enter recovery mode"), dom.on('click', async () => { await this._api.getDocAPI(this.currentDocId.get()!).recover(true); diff --git a/app/client/ui/ExampleInfo.ts b/app/client/ui/ExampleInfo.ts index b8f1c1f2..23fb1199 100644 --- a/app/client/ui/ExampleInfo.ts +++ b/app/client/ui/ExampleInfo.ts @@ -20,34 +20,34 @@ interface WelcomeCard { export const buildExamples = (): IExampleInfo[] => [{ id: 1, // Identifies the example in UserPrefs.seenExamples urlId: 'lightweight-crm', - title: t('Title', {context: "CRM"}), + title: t('Lightweight CRM'), imgUrl: 'https://www.getgrist.com/themes/grist/assets/images/use-cases/lightweight-crm.png', tutorialUrl: 'https://support.getgrist.com/lightweight-crm/', welcomeCard: { - title: t('WelcomeTitle', {context: "CRM"}), - text: t('WelcomeText', {context: "CRM"}), - tutorialName: t('WelcomeTutorialName', {context: "CRM"}), + title: t('Welcome to the Lightweight CRM template'), + text: t('Check out our related tutorial for how to link data, and create high-productivity layouts.'), + tutorialName: t("Tutorial: Create a CRM"), }, }, { id: 2, // Identifies the example in UserPrefs.seenExamples urlId: 'investment-research', - title: t('Title', {context: "investmentResearch"}), + title: t('Investment Research'), imgUrl: 'https://www.getgrist.com/themes/grist/assets/images/use-cases/data-visualization.png', tutorialUrl: 'https://support.getgrist.com/investment-research/', welcomeCard: { - title: t('WelcomeTitle', {context: "investmentResearch"}), - text: t('WelcomeText', {context: "investmentResearch"}), - tutorialName: t('WelcomeTutorialName', {context: "investmentResearch"}), + title: t("Welcome to the Investment Research template"), + text: t("Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically."), + tutorialName: t("Tutorial: Analyze & Visualize"), }, }, { id: 3, // Identifies the example in UserPrefs.seenExamples urlId: 'afterschool-program', - title: t('Title', {context: "afterschool"}), + title: t('Afterschool Program'), imgUrl: 'https://www.getgrist.com/themes/grist/assets/images/use-cases/business-management.png', tutorialUrl: 'https://support.getgrist.com/afterschool-program/', welcomeCard: { - title: t('WelcomeTitle', {context: "afterschool"}), - text: t('WelcomeText', {context: "afterschool"}), - tutorialName: t('WelcomeTutorialName', {context: "afterschool"}), + title: t("Welcome to the Afterschool Program template"), + text: t("Check out our related tutorial for how to model business data, use formulas, and manage complexity."), + tutorialName: t("Tutorial: Manage Business Data"), }, }]; diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 8a34e01f..43f926df 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -231,19 +231,19 @@ export function buildFormulaConfig( // Converts data column to formula column. const convertDataColumnToFormulaOption = () => selectOption( () => (maybeFormula.set(true), formulaField?.focus()), - t('ConvertColumn', {context: 'formula'}), 'Script'); + t("Clear and make into formula"), 'Script'); // Converts to empty column and opens up the editor. (label is the same, but this is used when we have no formula) const convertTriggerToFormulaOption = () => selectOption( () => gristDoc.convertIsFormula([origColumn.id.peek()], {toFormula: true, noRecalc: true}), - t('ConvertColumn', {context: 'formula'}), 'Script'); + t("Clear and make into formula"), 'Script'); // Convert column to data. // This method is also available through a text button. const convertToData = () => gristDoc.convertIsFormula([origColumn.id.peek()], {toFormula: false, noRecalc: true}); const convertToDataOption = () => selectOption( convertToData, - t('ConvertColumn', {context: 'data'}), 'Database', + t("Convert column to data"), 'Database', dom.cls('disabled', isSummaryTable) ); @@ -357,7 +357,7 @@ export function buildFormulaConfig( formulaBuilder(onSaveConvertToFormula), cssEmptySeparator(), cssRow(textButton( - t('ConvertColumn', {context: 'triggerformula'}), + t("Convert to trigger formula"), dom.on("click", convertFormulaToTrigger), dom.hide(maybeFormula), dom.prop("disabled", use => use(isSummaryTable) || use(disableOtherActions)), diff --git a/app/client/ui/FieldMenus.ts b/app/client/ui/FieldMenus.ts index 948c7845..177a7076 100644 --- a/app/client/ui/FieldMenus.ts +++ b/app/client/ui/FieldMenus.ts @@ -13,10 +13,10 @@ const t = makeT('FieldMenus'); export function FieldSettingsMenu(useColOptions: boolean, disableSeparate: boolean, actions: IFieldOptions) { useColOptions = useColOptions || disableSeparate; return [ - menuSubHeader(t('UsingSettings', {context: useColOptions ? 'common' : 'separate'})), - useColOptions ? menuItem(actions.useSeparate, t('Settings', {context: 'useseparate'}), dom.cls('disabled', disableSeparate)) : [ - menuItem(actions.saveAsCommon, t('Settings', {context: 'savecommon'})), - menuItem(actions.revertToCommon, t('Settings', {context: 'revertcommon'})), + menuSubHeader(useColOptions ? t("Using common settings") : t("Using separate settings")), + useColOptions ? menuItem(actions.useSeparate, t("Use separate settings"), dom.cls('disabled', disableSeparate)) : [ + menuItem(actions.saveAsCommon, t("Save as common settings")), + menuItem(actions.revertToCommon, t("Revert to common settings")), ] ]; } diff --git a/app/client/ui/errorPages.ts b/app/client/ui/errorPages.ts index 24d7c044..eecf5ed4 100644 --- a/app/client/ui/errorPages.ts +++ b/app/client/ui/errorPages.ts @@ -87,8 +87,8 @@ export function createOtherErrorPage(appModel: AppModel, message?: string) { document.title = t("Error{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())}); return pagePanelsError(appModel, t("Something went wrong"), [ - cssErrorText(message ? t('ErrorHappened', {context: 'message', message: addPeriod(message)}) : - t('ErrorHappened', {context: 'unknown'})), + cssErrorText(message ? t('There was an error: {{message}}', {message: addPeriod(message)}) : + t('There was an unknown error.')), cssButtonWrap(bigPrimaryButtonLink(t("Go to main page"), testId('error-primary-btn'), urlState().setLinkUrl({}))), cssButtonWrap(bigBasicButtonLink(t("Contact support"), {href: 'https://getgrist.com/contact'})), From e0ae321ff51f3d149edc38c8e73d4b94f970f9f0 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 17:23:29 +0100 Subject: [PATCH 08/17] Change translation keys for complex context keys --- app/client/ui/DocMenu.ts | 3 ++- app/client/ui/FieldConfig.ts | 18 +++++++++--------- app/client/ui/GridViewMenus.ts | 16 ++++++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/client/ui/DocMenu.ts b/app/client/ui/DocMenu.ts index 2c143d3c..465455cf 100644 --- a/app/client/ui/DocMenu.ts +++ b/app/client/ui/DocMenu.ts @@ -293,7 +293,8 @@ function buildOtherSites(home: HomeModel) { const siteName = home.app.currentOrgName; return [ dom('div', - t("You are on the {{siteName}} site. You also have access to the following sites:", { siteName, context: personal ? 'personal' : '' }), + personal ? t("You are on the {{siteName}} site. You also have access to the following sites:", {siteName}) : + t("You are on your personal site. You also have access to the following sites:"), testId('other-sites-message') ), css.otherSitesButtons( diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 43f926df..73123984 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -210,19 +210,19 @@ export function buildFormulaConfig( const behaviorName = Computed.create(owner, behavior, (use, type) => { if (use(isMultiSelect)) { const commonType = use(multiType); - if (commonType === 'formula') { return t('ColumnType', {context: 'formula', count: 2}); } - if (commonType === 'data') { return t('ColumnType', {context: 'data', count: 2}); } - if (commonType === 'mixed') { return t('ColumnType', {context: 'mixed', count: 2}); } - return t('ColumnType', {context: 'empty', count: 2}); + if (commonType === 'formula') { return t('Formula Column', {count: 2}); } + if (commonType === 'data') { return t('Data Column', {count: 2}); } + if (commonType === 'mixed') { return t('Mixed Behavior'); } + return t('Empty Column', {count: 2}); } else { - if (type === 'formula') { return t('ColumnType', {context: 'formula', count: 1}); } - if (type === 'data') { return t('ColumnType', {context: 'data', count: 1}); } - return t('ColumnType', {context: 'empty', count: 1}); + if (type === 'formula') { return t('Formula Column', {count: 1}); } + if (type === 'data') { return t('Data Column', {count: 1}); } + return t('Empty Column', {count: 1}); } }); const behaviorIcon = Computed.create(owner, (use) => { - return use(behaviorName) === t('ColumnType', {context: 'data', count: 2}) || - use(behaviorName) === t('ColumnType', {context: 'data', count: 1}) ? "Database" : "Script"; + return use(behaviorName) === t('Data Column', {count: 2}) || + use(behaviorName) === t('Data Column', {count: 1}) ? "Database" : "Script"; }); const behaviorLabel = () => selectTitle(behaviorName, behaviorIcon); diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts index 7d7f777c..7e795ba8 100644 --- a/app/client/ui/GridViewMenus.ts +++ b/app/client/ui/GridViewMenus.ts @@ -206,12 +206,14 @@ export function freezeAction(options: IMultiColumnContextMenu): { text: string; // if user clicked the first column or a column just after frozen set if (firstColumnIndex === 0 || firstColumnIndex === numFrozen) { - text = t('FreezeColumn', {count: 1}); + text = t('Freeze {{count}} columns', {count: 1}); } else { // else user clicked any other column that is farther, offer to freeze // proper number of column const properNumber = firstColumnIndex - numFrozen + 1; - text = t('FreezeColumn', {count: properNumber, context: numFrozen ? 'more' : '' }); + text = numFrozen ? + t('Freeze {{count}} more columns', {count: properNumber}) : + t('Freeze {{count}} columns', {count: properNumber}); } return { text, @@ -220,12 +222,14 @@ export function freezeAction(options: IMultiColumnContextMenu): { text: string; } else if (isFrozenColumn) { // when user clicked last column in frozen set - offer to unfreeze this column if (firstColumnIndex + 1 === numFrozen) { - text = t('UnfreezeColumn', {count: 1}); + text = t('Unfreeze {{count}} columns', {count: 1}); } else { // else user clicked column that is not the last in a frozen set // offer to unfreeze proper number of columns const properNumber = numFrozen - firstColumnIndex; - text = t('UnfreezeColumn', {count: properNumber, context: properNumber === numFrozen ? 'all' : '' }); + text = properNumber === numFrozen ? + t('Unfreeze all columns') : + t('UnFreeze {{count}} columns', {count: properNumber}); } return { text, @@ -249,7 +253,7 @@ export function freezeAction(options: IMultiColumnContextMenu): { text: string; }; } else if (isSpanSet) { const toFreeze = lastColumnIndex + 1 - numFrozen; - text = t('FreezeColumn', {count: toFreeze, context: 'more'}); + text = t('Freeze {{count}} more columns', {count: toFreeze}); return { text, numFrozen : numFrozen + toFreeze @@ -278,7 +282,7 @@ 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("Add to sort", {count: index + 1, context: 'added'}); + return t("Sorted (#{{count}})", {count: index + 1}); } else { return t("Add to sort"); } From 7082e3dce98f6fd90c7a518410d0fa5d0cbce051 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 6 Dec 2022 17:43:35 +0100 Subject: [PATCH 09/17] Change translation keys for keys with counts --- app/client/ui/CellContextMenu.ts | 10 +++++----- app/client/ui/CustomSectionConfig.ts | 2 +- app/client/ui/FieldConfig.ts | 16 ++++++++-------- app/client/ui/Pages.ts | 2 +- app/client/ui/RightPanel.ts | 8 ++++---- app/client/ui/RowContextMenu.ts | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/client/ui/CellContextMenu.ts b/app/client/ui/CellContextMenu.ts index 7194b926..5bd87de3 100644 --- a/app/client/ui/CellContextMenu.ts +++ b/app/client/ui/CellContextMenu.ts @@ -20,12 +20,12 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC const numCols: number = colOptions.numColumns; const nameClearColumns = colOptions.isFiltered ? - t("ResetEntireColumns", {count: numCols}) : - t("ResetColumns", {count: numCols}); - const nameDeleteColumns = t("DeleteColumns", {count: numCols}); + t("Reset {{count}} entire columns", {count: numCols}) : + t("Reset {{count}} columns", {count: numCols}); + const nameDeleteColumns = t("Delete {{count}} columns", {count: numCols}); const numRows: number = rowOptions.numRows; - const nameDeleteRows = t("DeleteRows", {count: numRows}); + const nameDeleteRows = t("Delete {{count}} rows", {count: numRows}); const nameClearCells = (numRows > 1 || numCols > 1) ? t("Clear values") : t("Clear cell"); @@ -68,7 +68,7 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC menuItemCmd(allCommands.insertRecordAfter, t("Insert row below"), dom.cls('disabled', disableInsert))] ), - menuItemCmd(allCommands.duplicateRows, t("DuplicateRows", {count: numRows}), + menuItemCmd(allCommands.duplicateRows, t("Duplicate rows", {count: numRows}), dom.cls('disabled', disableInsert || numRows === 0)), menuItemCmd(allCommands.insertFieldBefore, t("Insert column to the left"), disableForReadonlyView), diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts index 3d746036..f2792dd4 100644 --- a/app/client/ui/CustomSectionConfig.ts +++ b/app/client/ui/CustomSectionConfig.ts @@ -117,7 +117,7 @@ class ColumnListPicker extends Disposable { col.label.peek(), )), wrongTypeCount > 0 ? menuText( - t("WrongTypesMenuText", {wrongTypeCount, columnType: this._column.type.toLowerCase(), count: wrongTypeCount}), + t("{{wrongTypeCount}} non-{{columnType}} columns are not shown", {wrongTypeCount, columnType: this._column.type.toLowerCase(), count: wrongTypeCount}), testId('map-message-' + this._column.name) ) : null ]; diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 73123984..626ac49e 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -210,19 +210,19 @@ export function buildFormulaConfig( const behaviorName = Computed.create(owner, behavior, (use, type) => { if (use(isMultiSelect)) { const commonType = use(multiType); - if (commonType === 'formula') { return t('Formula Column', {count: 2}); } - if (commonType === 'data') { return t('Data Column', {count: 2}); } + if (commonType === 'formula') { return t('Formula Columns', {count: 2}); } + if (commonType === 'data') { return t('Data Columns', {count: 2}); } if (commonType === 'mixed') { return t('Mixed Behavior'); } - return t('Empty Column', {count: 2}); + return t('Empty Columns', {count: 2}); } else { - if (type === 'formula') { return t('Formula Column', {count: 1}); } - if (type === 'data') { return t('Data Column', {count: 1}); } - return t('Empty Column', {count: 1}); + if (type === 'formula') { return t('Formula Columns', {count: 1}); } + if (type === 'data') { return t('Data Columns', {count: 1}); } + return t('Empty Columns', {count: 1}); } }); const behaviorIcon = Computed.create(owner, (use) => { - return use(behaviorName) === t('Data Column', {count: 2}) || - use(behaviorName) === t('Data Column', {count: 1}) ? "Database" : "Script"; + return use(behaviorName) === t('Data Columns', {count: 2}) || + use(behaviorName) === t('Data Columns', {count: 1}) ? "Database" : "Script"; }); const behaviorLabel = () => selectTitle(behaviorName, behaviorIcon); diff --git a/app/client/ui/Pages.ts b/app/client/ui/Pages.ts index b6f97bc9..921fe72b 100644 --- a/app/client/ui/Pages.ts +++ b/app/client/ui/Pages.ts @@ -137,7 +137,7 @@ function buildPrompt(tableNames: string[], onSave: (option: RemoveOption) => Pro const saveDisabled = Computed.create(owner, use => use(selected) === ''); const saveFunc = () => onSave(selected.get()); return { - title: t('TableWillNoLongerBeVisible', { count: tableNames.length }), + title: t('The following tables will no longer be visible', { count: tableNames.length }), body: dom('div', testId('popup'), buildWarning(tableNames), diff --git a/app/client/ui/RightPanel.ts b/app/client/ui/RightPanel.ts index 793bc699..8aceb998 100644 --- a/app/client/ui/RightPanel.ts +++ b/app/client/ui/RightPanel.ts @@ -56,11 +56,11 @@ const PageSubTab = StringUnion("widget", "sortAndFilter", "data"); export function getFieldType(widgetType: IWidgetType|null) { // A map of widget type to the icon and label to use for a field of that widget. const fieldTypes = new Map([ - ['record', {label: t('Column', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Column', { count: 2 })}], - ['detail', {label: t('Field', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Field', { count: 2 })}], - ['single', {label: t('Field', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Field', { count: 2 })}], + ['record', {label: t('Columns', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Columns', { count: 2 })}], + ['detail', {label: t('Fields', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Fields', { count: 2 })}], + ['single', {label: t('Fields', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Fields', { count: 2 })}], ['chart', {label: t('Series', { count: 1 }), icon: 'ChartLine', pluralLabel: t('Series', { count: 2 })}], - ['custom', {label: t('Column', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Column', { count: 2 })}], + ['custom', {label: t('Columns', { count: 1 }), icon: 'TypeCell', pluralLabel: t('Columns', { count: 2 })}], ]); return fieldTypes.get(widgetType || 'record') || fieldTypes.get('record')!; diff --git a/app/client/ui/RowContextMenu.ts b/app/client/ui/RowContextMenu.ts index f63b68ab..f05cb255 100644 --- a/app/client/ui/RowContextMenu.ts +++ b/app/client/ui/RowContextMenu.ts @@ -31,7 +31,7 @@ export function RowContextMenu({ disableInsert, disableDelete, isViewSorted, num ); } result.push( - menuItemCmd(allCommands.duplicateRows, t('DuplicateRows', { count: numRows }), + menuItemCmd(allCommands.duplicateRows, t('Duplicate rows', { count: numRows }), dom.cls('disabled', disableInsert || numRows === 0)), ); result.push( From 1d97137f11dc76a9b18b980902dd42f9c2430730 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Fri, 9 Dec 2022 15:09:36 +0100 Subject: [PATCH 10/17] Generated translation keys from code file --- app/client/aclui/ACLUsers.ts | 8 +- static/locales/en.client.json | 1315 ++++++++++++++++----------------- 2 files changed, 642 insertions(+), 681 deletions(-) diff --git a/app/client/aclui/ACLUsers.ts b/app/client/aclui/ACLUsers.ts index 7ef4f871..1c6ef2c0 100644 --- a/app/client/aclui/ACLUsers.ts +++ b/app/client/aclui/ACLUsers.ts @@ -15,7 +15,7 @@ import {cssMenu, cssMenuWrap, defaultMenuOptions, IPopupOptions, setPopupToCreat import {getUserRoleText} from 'app/common/UserAPI'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('aclui.ViewAsDropdown'); +const t = makeT("ACLUsers"); function isSpecialEmail(email: string) { return email === ANONYMOUS_USER_EMAIL || email === EVERYONE_EMAIL; @@ -54,15 +54,15 @@ export class ACLUsersPopup extends Disposable { return cssMenuWrap(cssMenu( dom.cls(menuCssClass), cssUsers.cls(''), - cssHeader(t('ViewAs'), dom.show(this._shareUsers.length > 0)), + cssHeader(t('View As'), dom.show(this._shareUsers.length > 0)), dom.forEach(this._shareUsers, buildRow), - (this._attributeTableUsers.length > 0) ? cssHeader(t('UsersFrom')) : null, + (this._attributeTableUsers.length > 0) ? cssHeader(t("Users from table")) : null, dom.forEach(this._attributeTableUsers, buildExampleUserRow), // Include example users only if there are not many "real" users. // It might be better to have an expandable section with these users, collapsed // by default, but that's beyond my UI ken. (this._shareUsers.length + this._attributeTableUsers.length < 5) ? [ - (this._exampleUsers.length > 0) ? cssHeader(t('ExampleUsers')) : null, + (this._exampleUsers.length > 0) ? cssHeader(t("Example Users")) : null, dom.forEach(this._exampleUsers, buildExampleUserRow) ] : null, (el) => { setTimeout(() => el.focus(), 0); }, diff --git a/static/locales/en.client.json b/static/locales/en.client.json index dbdaeeab..dedd22f3 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -1,361 +1,455 @@ { - "AccountPage": { - "AccountSettings":"Account settings", - "API":"API", - "Edit":"Edit", - "Email":"Email", - "Name":"Name", - "Save":"Save", - "PasswordSecurity":"Password & Security", - "LoginMethod":"Login Method", - "ChangePassword":"Change Password", - "AllowGoogleSigning":"Allow signing in to this account with Google", - "TwoFactorAuth": "Two-factor authentication", - "TwoFactorAuthDescription":"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.", - "Theme":"Theme", - "APIKey":"API Key", - "WarningUsername":"Names only allow letters, numbers and certain special characters" - + "ACUserManager": { + "Enter email address": "Enter email address", + "Invite new member": "Invite new member", + "We'll email an invite to {{email}}": "We'll email an invite to {{email}}" }, - "AccountWidget":{ - "SignIn":"Sign in", - "DocumentSettings":"Document Settings", - "ToggleMobileMode":"Toggle Mobile Mode", - "Pricing":"Pricing", - "ProfileSettings":"Profile Settings", - "ManageTeam":"Manage Team", - "AccessDetails":"Access Details", - "SwitchAccounts":"Switch Accounts", - "Accounts":"Accounts", - "AddAccount":"Add Account", - "SignOut":"Sign Out" + "AccessRules": { + "Add Column Rule": "Add Column Rule", + "Add Default Rule": "Add Default Rule", + "Add Table Rules": "Add Table Rules", + "Add User Attributes": "Add User Attributes", + "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.", + "Allow everyone to view Access Rules.": "Allow everyone to view Access Rules.", + "Attribute name": "Attribute name", + "Attribute to Look Up": "Attribute to Look Up", + "Checking...": "Checking...", + "Condition": "Condition", + "Default Rules": "Default Rules", + "Delete Table Rules": "Delete Table Rules", + "Enter Condition": "Enter Condition", + "Everyone": "Everyone", + "Everyone Else": "Everyone Else", + "Invalid": "Invalid", + "Lookup Column": "Lookup Column", + "Lookup Table": "Lookup Table", + "Permission to access the document in full when needed": "Permission to access the document in full when needed", + "Permission to view Access Rules": "Permission to view Access Rules", + "Permissions": "Permissions", + "Reset": "Reset", + "Rules for table ": "Rules for table ", + "Save": "Save", + "Saved": "Saved", + "Special Rules": "Special Rules", + "User Attributes": "User Attributes", + "View As": "View As" + }, + "AccountPage": { + "API": "API", + "API Key": "API Key", + "Account settings": "Account settings", + "Allow signing in to this account with Google": "Allow signing in to this account with Google", + "Change Password": "Change Password", + "Edit": "Edit", + "Email": "Email", + "Login Method": "Login Method", + "Name": "Name", + "Names only allow letters, numbers and certain special characters": "Names only allow letters, numbers and certain special characters", + "Password & Security": "Password & Security", + "Save": "Save", + "Theme": "Theme", + "Two-factor authentication": "Two-factor authentication", + "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.": "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." + }, + "AccountWidget": { + "Access Details": "Access Details", + "Accounts": "Accounts", + "Add Account": "Add Account", + "Document Settings": "Document Settings", + "Manage Team": "Manage Team", + "Pricing": "Pricing", + "Profile Settings": "Profile Settings", + "Sign Out": "Sign Out", + "Sign in": "Sign in", + "Switch Accounts": "Switch Accounts", + "Toggle Mobile Mode": "Toggle Mobile Mode" + }, + "ViewAsDropdown": { + "View As": "View As", + "Users from table": "Users from table", + "Example Users": "Example Users" + }, + "ActionLog": { + "Action Log failed to load": "Action Log failed to load", + "Column {{colId}} was subsequently removed in action #{{action.actionNum}}": "Column {{colId}} was subsequently removed in action #{{action.actionNum}}", + "Table {{tableId}} was subsequently removed in action #{{actionNum}}": "Table {{tableId}} was subsequently removed in action #{{actionNum}}", + "This row was subsequently removed in action {{action.actionNum}}": "This row was subsequently removed in action {{action.actionNum}}" }, "AddNewButton": { - "AddNew": "Add New" - }, - "AppHeader": { - "HomePage": "Home Page", - "Legacy": "Legacy", - "PersonalSite": "Personal Site", - "TeamSite": "Team Site" + "Add New": "Add New" }, "ApiKey": { - "AboutToDeleteAPIKey": "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?", - "AnonymousAPIKey": "This API key can be used to access this account anonymously via the API.", - "ByGenerating": "By generating an API key, you will be able to make API calls for your own account.", - "ClickToShow": "Click to show", + "By generating an API key, you will be able to make API calls for your own account.": "By generating an API key, you will be able to make API calls for your own account.", + "Click to show": "Click to show", "Create": "Create", - "OwnAPIKey": "This API key can be used to access your account via the API. Don’t share your API key with anyone.", "Remove": "Remove", - "RemoveAPIKey": "Remove API Key" + "Remove API Key": "Remove API Key", + "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?": "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?" }, "App": { "Description": "Description", "Key": "Key", - "MemoryError": "Memory Error" + "Memory Error": "Memory Error" + }, + "AppHeader": { + "Home Page": "Home Page", + "Legacy": "Legacy", + "Personal Site": "Personal Site", + "Team Site": "Team Site" + }, + "AppModel": { + "This team site is suspended. Documents can be read, but not modified.": "This team site is suspended. Documents can be read, but not modified." }, "CellContextMenu": { - "ResetEntireColumns_one": "Reset entire column", - "ResetEntireColumns_other": "Reset {{count}} entire columns", - "ResetColumns_one": "Reset column", - "ResetColumns_other": "Reset {{count}} columns", - "DeleteColumns_one": "Delete column", - "DeleteColumns_other": "Delete {{count}} columns", - "DeleteRows_one": "Delete row", - "DeleteRows_other": "Delete {{count}} rows", - "ClearValues": "Clear values", - "ClearCell": "Clear cell", - "CopyAnchorLink": "Copy anchor link", - "FilterByValue": "Filter by this value", - "InsertRow": "Insert row", - "InsertRowAbove": "Insert row above", - "InsertRowBelow": "Insert row below", - "DuplicateRows_one": "Duplicate row", - "DuplicateRows_other": "Duplicate rows", - "InsertColumnRight": "Insert column to the right", - "InsertColumnLeft": "Insert column to the left" + "Clear cell": "Clear cell", + "Clear values": "Clear values", + "Copy anchor link": "Copy anchor link", + "Delete {{count}} columns_one": "Delete {{count}} columns", + "Delete {{count}} columns_other": "Delete {{count}} columns", + "Delete {{count}} rows_one": "Delete {{count}} rows", + "Delete {{count}} rows_other": "Delete {{count}} rows", + "Duplicate rows_one": "Duplicate rows", + "Duplicate rows_other": "Duplicate rows", + "Filter by this value": "Filter by this value", + "Insert column to the left": "Insert column to the left", + "Insert column to the right": "Insert column to the right", + "Insert row": "Insert row", + "Insert row above": "Insert row above", + "Insert row below": "Insert row below", + "Reset {{count}} columns_one": "Reset {{count}} columns", + "Reset {{count}} columns_other": "Reset {{count}} columns", + "Reset {{count}} entire columns_one": "Reset {{count}} entire columns", + "Reset {{count}} entire columns_other": "Reset {{count}} entire columns" + }, + "ChartView": { + "Create separate series for each value of the selected column.": "Create separate series for each value of the selected column.", + "Each Y series is followed by a series for the length of error bars.": "Each Y series is followed by a series for the length of error bars.", + "Each Y series is followed by two series, for top and bottom error bars.": "Each Y series is followed by two series, for top and bottom error bars.", + "Pick a column": "Pick a column", + "Toggle chart aggregation": "Toggle chart aggregation", + "selected new group data columns": "selected new group data columns" + }, + "CodeEditorPanel": { + "Access denied": "Access denied", + "Code View is available only when you have full document access.": "Code View is available only when you have full document access." + }, + "ColorSelect": { + "Apply": "Apply", + "Cancel": "Cancel", + "Default cell style": "Default cell style" }, "ColumnFilterMenu": { - "Search": "Search", - "SearchValues": "Search values", "All": "All", - "AllShown": "All Shown", - "AllExcept": "All Except", + "All Except": "All Except", + "All Shown": "All Shown", + "Filter by Range": "Filter by Range", + "Future Values": "Future Values", + "No matching values": "No matching values", "None": "None", - "NoMatchingValues": "No matching values", - "OtherMatching": "Other Matching", - "OtherNonMatching": "Other Non-Matching", - "OtherValues": "Other Values", - "FutureValues": "Future Values", + "Min": "Min", + "Max": "Max", + "Start": "Start", + "End": "End", + "Other Matching": "Other Matching", + "Other Non-Matching": "Other Non-Matching", + "Other Values": "Other Values", "Others": "Others", - "RangeMin": "Min", - "RangeMax": "Max", - "DateRangeMin": "Start", - "DateRangeMax": "End" + "Search": "Search", + "Search values": "Search values" }, "CustomSectionConfig": { + " (optional)": " (optional)", "Add": "Add", - "EnterCustomURL": "Enter Custom URL", - "FullDocumentAccess": "Full document access", - "LearnMore": "Learn more about custom widgets", - "PickAColumn": "Pick a column", - "PickAColumnWithType": "Pick a {{columnType}} column", - "NoDocumentAccess": "No document access", - "OpenConfiguration": "Open configuration", - "Optional": " (optional)", - "ReadSelectedTable": "Read selected table", - "SelectCustomWidget": "Select Custom Widget", - "WidgetNeedFullAccess": "Widget needs {{fullAccess}} to this document.", - "WidgetNeedRead": "Widget needs to {{read}} the current table.", - "WidgetNoPermissison": "Widget does not require any permissions.", - "WrongTypesMenuText_one": "{{wrongTypeCount}} non-{{columnType}} column is not shown", - "WrongTypesMenuText_others": "{{wrongTypeCount}} non-{{columnType}} columns are not shown" + "Enter Custom URL": "Enter Custom URL", + "Full document access": "Full document access", + "Learn more about custom widgets": "Learn more about custom widgets", + "No document access": "No document access", + "Open configuration": "Open configuration", + "Pick a column": "Pick a column", + "Pick a {{columnType}} column": "Pick a {{columnType}} column", + "Read selected table": "Read selected table", + "Select Custom Widget": "Select Custom Widget", + "Widget does not require any permissions.": "Widget does not require any permissions.", + "Widget needs to {{read}} the current table.": "Widget needs to {{read}} the current table.", + "Widget needs {{fullAccess}} to this document.": "Widget needs {{fullAccess}} to this document.", + "{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "{{wrongTypeCount}} non-{{columnType}} columns are not shown", + "{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "{{wrongTypeCount}} non-{{columnType}} columns are not shown" + }, + "DataTables": { + "Click to copy": "Click to copy", + "Delete {{formattedTableName}} data, and remove it from all pages?": "Delete {{formattedTableName}} data, and remove it from all pages?", + "Duplicate Table": "Duplicate Table", + "Raw Data Tables": "Raw Data Tables", + "Table ID copied to clipboard": "Table ID copied to clipboard", + "You do not have edit access to this document": "You do not have edit access to this document" }, "DocHistory": { "Activity": "Activity", + "Beta": "Beta", + "Compare to Current": "Compare to Current", + "Compare to Previous": "Compare to Previous", + "Open Snapshot": "Open Snapshot", "Snapshots": "Snapshots", - "SnapshotsUnavailable": "Snapshots are unavailable.", - "OpenSnapshot": "Open Snapshot", - "CompareToCurrent": "Compare to Current", - "CompareToPrevious": "Compare to Previous", - "Beta": "Beta" + "Snapshots are unavailable.": "Snapshots are unavailable." }, "DocMenu": { - "OtherSites": "Other Sites", - "OtherSitesWelcome": "You are on the {{siteName}} site. You also have access to the following sites:", - "OtherSitesWelcome_personal": "You are on your personal site. You also have access to the following sites:", - "AllDocuments": "All Documents", - "ExamplesAndTemplates": "Examples and Templates", - "MoreExamplesAndTemplates": "More Examples and Templates", - "ServiceNotAvailable": "This service is not available right now", - "NeedPaidPlan": "(The organization needs a paid plan)", - "PinnedDocuments": "Pinned Documents", - "Featured": "Featured", - "Trash": "Trash", - "DocStayInTrash": "Documents stay in Trash for 30 days, after which they get deleted permanently.", - "EmptyTrash": "Trash is empty.", - "WorkspaceNotFound": "Workspace not found", + "(The organization needs a paid plan)": "(The organization needs a paid plan)", + "Access Details": "Access Details", + "All Documents": "All Documents", + "By Date Modified": "By Date Modified", + "By Name": "By Name", + "Current workspace": "Current workspace", "Delete": "Delete", - "DeleteDoc": "Delete {{name}}", - "Deleted": "Deleted {{at}}", - "Edited": "Edited {{at}}", - "Examples&Templates": "Examples & Templates", - "DiscoverMoreTemplates": "Discover More Templates", - "ByName": "By Name", - "ByDateModified": "By Date Modified", - "DocumentMoveToTrash": "Document will be moved to Trash.", - "Rename": "Rename", + "Delete Forever": "Delete Forever", + "Delete {{name}}": "Delete {{name}}", + "Deleted {{at}}": "Deleted {{at}}", + "Discover More Templates": "Discover More Templates", + "Document will be moved to Trash.": "Document will be moved to Trash.", + "Document will be permanently deleted.": "Document will be permanently deleted.", + "Documents stay in Trash for 30 days, after which they get deleted permanently.": "Documents stay in Trash for 30 days, after which they get deleted permanently.", + "Edited {{at}}": "Edited {{at}}", + "Examples & Templates": "Examples & Templates", + "Examples and Templates": "Examples and Templates", + "Featured": "Featured", + "Manage Users": "Manage Users", + "More Examples and Templates": "More Examples and Templates", "Move": "Move", + "Move {{name}} to workspace": "Move {{name}} to workspace", + "Other Sites": "Other Sites", + "Permanently Delete \"{{name}}\"?": "Permanently Delete \"{{name}}\"?", + "Pin Document": "Pin Document", + "Pinned Documents": "Pinned Documents", "Remove": "Remove", - "UnpinDocument": "Unpin Document", - "PinDocument": "Pin Document", - "AccessDetails": "Access Details", - "ManageUsers": "Manage Users", - "DeleteForeverDoc": "Permanently Delete \"{{name}}\"?", - "DeleteForever": "Delete Forever", - "DeleteDocPerma": "Document will be permanently deleted.", + "Rename": "Rename", + "Requires edit permissions": "Requires edit permissions", "Restore": "Restore", - "RestoreThisDocument": "To restore this document, restore the workspace first.", - "DeleteWorkspaceForever": "You may delete a workspace forever once it has no documents in it.", - "CurrentWorkspace": "Current workspace", - "RequiresEditPermissions": "Requires edit permissions", - "MoveDocToWorkspace": "Move {{name}} to workspace" + "This service is not available right now": "This service is not available right now", + "To restore this document, restore the workspace first.": "To restore this document, restore the workspace first.", + "Trash": "Trash", + "Trash is empty.": "Trash is empty.", + "Unpin Document": "Unpin Document", + "Workspace not found": "Workspace not found", + "You are on the {{siteName}} site. You also have access to the following sites:": "You are on the {{siteName}} site. You also have access to the following sites:", + "You are on your personal site. You also have access to the following sites:": "You are on your personal site. You also have access to the following sites:", + "You may delete a workspace forever once it has no documents in it.": "You may delete a workspace forever once it has no documents in it." + }, + "DocPageModel": { + "Add Empty Table": "Add Empty Table", + "Add Page": "Add Page", + "Add Widget to Page": "Add Widget to Page", + "Document owners can attempt to recover the document. [{{error}}]": "Document owners can attempt to recover the document. [{{error}}]", + "Enter recovery mode": "Enter recovery mode", + "Error accessing document": "Error accessing document", + "Reload": "Reload", + "Sorry, access to this document has been denied. [{{error}}]": "Sorry, access to this document has been denied. [{{error}}]", + "You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]": "You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]", + "You do not have edit access to this document": "You do not have edit access to this document" }, "DocTour": { - "InvalidDocTourTitle": "No valid document tour", - "InvalidDocTourBody": "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." + "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.": "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.", + "No valid document tour": "No valid document tour" }, "DocumentSettings": { - "DocumentSettings": "Document Settings", - "ThisDocumentID": "This document's ID (for API use):", - "TimeZone": "Time Zone:", - "Locale": "Locale:", - "Currency": "Currency:", - "LocalCurrency": "Local currency ({{currency}})", - "EngineRisk": "Engine (experimental {{span}} change at own risk):", + "Currency:": "Currency:", + "Document Settings": "Document Settings", + "Engine (experimental {{span}} change at own risk):": "Engine (experimental {{span}} change at own risk):", + "Local currency ({{currency}})": "Local currency ({{currency}})", + "Locale:": "Locale:", "Save": "Save", - "SaveAndReload": "Save and Reload" + "Save and Reload": "Save and Reload", + "This document's ID (for API use):": "This document's ID (for API use):", + "Time Zone:": "Time Zone:" + }, + "DocumentUsage": { + "Attachments Size": "Attachments Size", + "Contact the site owner to upgrade the plan to raise limits.": "Contact the site owner to upgrade the plan to raise limits.", + "Data Size": "Data Size", + "For higher limits, ": "For higher limits, ", + "Rows": "Rows", + "Usage": "Usage", + "Usage statistics are only available to users with full access to the document data.": "Usage statistics are only available to users with full access to the document data.", + "start your 30-day free trial of the Pro plan.": "start your 30-day free trial of the Pro plan." + }, + "Drafts": { + "Restore last edit": "Restore last edit", + "Undo discard": "Undo discard" }, "DuplicateTable": { - "NewName": "Name for new table", - "AdviceWithLink": "Instead of duplicating tables, it's usually better to segment data using linked views. {{link}}", - "CopyAllData": "Copy all data in addition to the table structure.", - "WarningACL": "Only the document default access rules will apply to the copy." - }, - "errorPages": { - "AccessDenied": "Access denied{{suffix}}", - "DeniedOrganizationDocuments": "You do not have access to this organization's documents.", - "SignInWithDifferentAccount": "You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.", - "SignInToAccess": "Sign in to access this organization's documents.", - "GoToMainPage": "Go to main page", - "SignIn": "Sign in", - "AddAcount": "Add account", - "SignedOut": "Signed out{{suffix}}", - "GenericError": "Error{{suffix}}", - "SignedOutNow": "You are now signed out.", - "SignedInAgain": "Sign in again", - "PageNotFound": "Page not found{{suffix}}", - "NotFoundMainText": "The requested page could not be found.{{separator}}Please check the URL and try again.", - "ContactSupport": "Contact support", - "SomethingWentWrong": "Something went wrong", - "ErrorHappened_message": "There was an error: {{message}}", - "ErrorHappened_unknown": "There was an unknown error." - }, - "FieldConfig": { - "ColumnLabel": "COLUMN LABEL AND ID", - "ColumnOptionsLimited": "Column options are limited in summary tables.", - "ColumnType_formula_one": "Formula Column", - "ColumnType_data_one": "Data Column", - "ColumnType_empty_one": "Empty Column", - "ColumnType_formula_other": "Formula Columns", - "ColumnType_data_other": "Data Columns", - "ColumnType_empty_other": "Empty Columns", - "ColumnType_mixed_other": "Mixed Behavior", - "ConvertColumn_formula": "Clear and make into formula", - "ConvertColumn_data": "Convert column to data", - "ConvertColumn_triggerformula": "Convert to trigger formula", - "ClearAndReset": "Clear and reset", - "EnterFormula": "Enter formula", - "ColumnBehavior": "COLUMN BEHAVIOR", - "SetFormula": "Set formula", - "SetTriggerFormula": "Set trigger formula", - "MakeIntoDataColumn": "Make into data column", - "TriggerFormula": "TRIGGER FORMULA" + "Copy all data in addition to the table structure.": "Copy all data in addition to the table structure.", + "Instead of duplicating tables, it's usually better to segment data using linked views. {{link}}": "Instead of duplicating tables, it's usually better to segment data using linked views. {{link}}", + "Name for new table": "Name for new table", + "Only the document default access rules will apply to the copy.": "Only the document default access rules will apply to the copy." }, "ExampleInfo": { - "Title_CRM": "Lightweight CRM", - "WelcomeTitle_CRM": "Welcome to the Lightweight CRM template", - "WelcomeText_CRM": "Check out our related tutorial for how to link data, and create high-productivity layouts.", - "WelcomeTutorialName_CRM": "Tutorial: Create a CRM", - "Title_investmentResearch": "Investment Research", - "WelcomeTitle_investmentResearch": "Welcome to the Investment Research template", - "WelcomeText_investmentResearch": "Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.", - "WelcomeTutorialName_investmentResearch": "Tutorial: Analyze & Visualize", - "Title_afterschool": "Afterschool Program", - "WelcomeTitle_afterschool": "Welcome to the Afterschool Program template", - "WelcomeText_afterschool": "Check out our related tutorial for how to model business data, use formulas, and manage complexity.", - "WelcomeTutorialName_afterschool": "Tutorial: Manage Business Data" + "Afterschool Program": "Afterschool Program", + "Check out our related tutorial for how to link data, and create high-productivity layouts.": "Check out our related tutorial for how to link data, and create high-productivity layouts.", + "Check out our related tutorial for how to model business data, use formulas, and manage complexity.": "Check out our related tutorial for how to model business data, use formulas, and manage complexity.", + "Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.": "Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.", + "Investment Research": "Investment Research", + "Lightweight CRM": "Lightweight CRM", + "Tutorial: Analyze & Visualize": "Tutorial: Analyze & Visualize", + "Tutorial: Create a CRM": "Tutorial: Create a CRM", + "Tutorial: Manage Business Data": "Tutorial: Manage Business Data", + "Welcome to the Afterschool Program template": "Welcome to the Afterschool Program template", + "Welcome to the Investment Research template": "Welcome to the Investment Research template", + "Welcome to the Lightweight CRM template": "Welcome to the Lightweight CRM template" + }, + "FieldConfig": { + "COLUMN BEHAVIOR": "COLUMN BEHAVIOR", + "COLUMN LABEL AND ID": "COLUMN LABEL AND ID", + "Clear and make into formula": "Clear and make into formula", + "Clear and reset": "Clear and reset", + "Column options are limited in summary tables.": "Column options are limited in summary tables.", + "Convert column to data": "Convert column to data", + "Convert to trigger formula": "Convert to trigger formula", + "Data Columns_one": "Data Columns", + "Data Columns_other": "Data Columns", + "Empty Columns_one": "Empty Columns", + "Empty Columns_other": "Empty Columns", + "Enter formula": "Enter formula", + "Formula Columns_one": "Formula Columns", + "Formula Columns_other": "Formula Columns", + "Make into data column": "Make into data column", + "Mixed Behavior": "Mixed Behavior", + "Set formula": "Set formula", + "Set trigger formula": "Set trigger formula", + "TRIGGER FORMULA": "TRIGGER FORMULA" }, "FieldMenus": { - "UsingSettings_common": "Using common settings", - "UsingSettings_separate": "Using separate settings", - "Settings_useseparate": "Use separate settings", - "Settings_savecommon": "Save as common settings", - "Settings_revertcommon": "Revert to common settings" + "Revert to common settings": "Revert to common settings", + "Save as common settings": "Save as common settings", + "Use separate settings": "Use separate settings", + "Using common settings": "Using common settings", + "Using separate settings": "Using separate settings" }, - "FilterConfig":{ - "AddColumn": "Add Column" + "FilterConfig": { + "Add Column": "Add Column" }, "GridOptions": { - "GridOptions": "Grid Options", - "VerticalGridlines": "Vertical Gridlines", - "HorizontalGridlines": "Horizontal Gridlines", - "ZebraStripes": "Zebra Stripes" + "Grid Options": "Grid Options", + "Horizontal Gridlines": "Horizontal Gridlines", + "Vertical Gridlines": "Vertical Gridlines", + "Zebra Stripes": "Zebra Stripes" }, "GridViewMenus": { - "AddColumn": "Add Column", - "ShowColumn": "Show column {{- label}}", - "ColumnOptions": "Column Options", - "FilterData": "Filter Data", + "Add Column": "Add Column", + "Add to sort": "Add to sort", + "Clear values": "Clear values", + "Column Options": "Column Options", + "Convert formula to data": "Convert formula to data", + "DeleteColumns_one": "DeleteColumns", + "DeleteColumns_other": "DeleteColumns", + "Filter Data": "Filter Data", + "Freeze {{count}} columns_one": "Freeze {{count}} columns", + "Freeze {{count}} columns_other": "Freeze {{count}} columns", + "Freeze {{count}} more columns_one": "Freeze {{count}} more columns", + "Freeze {{count}} more columns_other": "Freeze {{count}} more columns", + "FreezeColumn_one": "FreezeColumn", + "FreezeColumn_other": "FreezeColumn", + "HideColumns_one": "HideColumns", + "HideColumns_other": "HideColumns", + "Insert column to the {{to}}": "Insert column to the {{to}}", + "More sort options ...": "More sort options ...", + "Rename column": "Rename column", + "ResetColumns_one": "ResetColumns", + "ResetColumns_other": "ResetColumns", + "ResetEntireColumns_one": "ResetEntireColumns", + "ResetEntireColumns_other": "ResetEntireColumns", + "Show column {{- label}}": "Show column {{- label}}", "Sort": "Sort", - "MoreSortOptions": "More sort options ...", - "RenameColumn": "Rename column", - "ResetEntireColumns_one": "Reset entire column", - "ResetEntireColumns_other": "Reset {{count}} entire columns", - "ResetColumns_one": "Reset column", - "ResetColumns_other": "Reset {{count}} columns", - "DeleteColumns_one": "Delete column", - "DeleteColumns_other": "Delete {{count}} columns", - "HideColumns_one": "Hide column", - "HideColumns_other": "Hide {{count}} columns", - "ConvertFormulaToData": "Convert formula to data", - "ClearValues": "Clear values", - "InsertColumn": "Insert column to the {{to}}", - "FreezeColumn_one": "Freeze this column", - "FreezeColumn_other": "Freeze {{count}} columns", - "FreezeColumn_more_one": "Freeze one more columns", - "FreezeColumn_more_other": "Freeze {{count}} more columns", - "UnfreezeColumn_one": "Unfreeze this column", - "UnfreezeColumn_other": "Unfreeze {{count}} columns", - "UnfreezeColumn_all_other": "Unfreeze all columns", - "AddToSort": "Add to sort", - "AddToSort_added": "Sorted (#{{count}})" + "Sorted (#{{count}})_one": "Sorted (#{{count}})", + "Sorted (#{{count}})_other": "Sorted (#{{count}})", + "UnFreeze {{count}} columns_one": "UnFreeze {{count}} columns", + "UnFreeze {{count}} columns_other": "UnFreeze {{count}} columns", + "Unfreeze all columns": "Unfreeze all columns", + "Unfreeze {{count}} columns_one": "Unfreeze {{count}} columns", + "Unfreeze {{count}} columns_other": "Unfreeze {{count}} columns", + "UnfreezeColumn_one": "UnfreezeColumn", + "UnfreezeColumn_other": "UnfreezeColumn" + }, + "GristDoc": { + "Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}", + "Import from file": "Import from file", + "Saved linked section {{title}} in view {{name}}": "Saved linked section {{title}} in view {{name}}" }, "HomeIntro": { - "SignUp": "Sign up", - "EmptyWorkspace": "This workspace is empty.", - "PersonalSite": "personal site", - "WelcomeTo": "Welcome to {{orgName}}", - "WelcomeInfoNoDocuments": "You have read-only access to this site. Currently there are no documents.", - "WelcomeInfoAppearHere": "Any documents created in this site will appear here.", - "WelcomeTextVistGrist": "Interested in using Grist outside of your team? Visit your free ", - "SproutsProgram": "Sprouts Program", - "WelcomeUser": "Welcome to Grist, {{name}}!", - "TeamSiteIntroGetStarted": "Get started by inviting your team and creating your first Grist document.", - "OrFindAndExpert": ", or find an expert via our ", - "PersonalIntroGetStarted": "Get started by creating your first Grist document.", - "AnonIntroGetStarted": "Get started by exploring templates, or creating your first Grist document.", - "Welcome": "Welcome to Grist!", - "VisitHelpCenter": "Visit our {{link}} to learn more.", - "HelpCenter": "Help Center", - "InviteTeamMembers": "Invite Team Members", - "BrowseTemplates": "Browse Templates", - "CreateEmptyDocument": "Create Empty Document", - "ImportDocument": "Import Document" + "Any documents created in this site will appear here.": "Any documents created in this site will appear here.", + "Browse Templates": "Browse Templates", + "Create Empty Document": "Create Empty Document", + "Get started by creating your first Grist document.": "Get started by creating your first Grist document.", + "Get started by exploring templates, or creating your first Grist document.": "Get started by exploring templates, or creating your first Grist document.", + "Get started by inviting your team and creating your first Grist document.": "Get started by inviting your team and creating your first Grist document.", + "Help Center": "Help Center", + "Import Document": "Import Document", + "Interested in using Grist outside of your team? Visit your free ": "Interested in using Grist outside of your team? Visit your free ", + "Invite Team Members": "Invite Team Members", + "Sign up": "Sign up", + "Sprouts Program": "Sprouts Program", + "This workspace is empty.": "This workspace is empty.", + "Visit our {{link}} to learn more.": "Visit our {{link}} to learn more.", + "Welcome to Grist!": "Welcome to Grist!", + "Welcome to Grist, {{name}}!": "Welcome to Grist, {{name}}!", + "Welcome to {{orgName}}": "Welcome to {{orgName}}", + "You have read-only access to this site. Currently there are no documents.": "You have read-only access to this site. Currently there are no documents.", + "personal site": "personal site" }, "HomeLeftPane": { - "AllDocuments": "All Documents", - "ExamplesAndTemplates": "Examples & Templates", - "CreateEmptyDocument": "Create Empty Document", - "ImportDocument": "Import Document", - "CreateWorkspace": "Create Workspace", - "Trash": "Trash", - "Rename": "Rename", + "Access Details": "Access Details", + "All Documents": "All Documents", + "Create Empty Document": "Create Empty Document", + "Create Workspace": "Create Workspace", "Delete": "Delete", - "Workspaces": "Workspaces", - "WorkspaceDeleteTitle": "Delete {{workspace}} and all included documents?", - "WorkspaceDeleteText": "Workspace will be moved to Trash.", - "ManageUsers": "Manage Users", - "AccessDetails": "Access Details" + "Delete {{workspace}} and all included documents?": "Delete {{workspace}} and all included documents?", + "Examples & Templates": "Examples & Templates", + "Import Document": "Import Document", + "Manage Users": "Manage Users", + "Rename": "Rename", + "Trash": "Trash", + "Workspace will be moved to Trash.": "Workspace will be moved to Trash.", + "Workspaces": "Workspaces" + }, + "Importer": { + "Merge rows that match these fields:": "Merge rows that match these fields:", + "Select fields to match on": "Select fields to match on", + "Update existing records": "Update existing records" }, "LeftPanelCommon": { - "HelpCenter": "Help Center" + "Help Center": "Help Center" }, "MakeCopyMenu": { - "CannotEditOriginal": "Replacing the original requires editing rights on the original document.", + "As Template": "As Template", + "Be careful, the original has changes not in this document. Those changes will be overwritten.": "Be careful, the original has changes not in this document. Those changes will be overwritten.", "Cancel": "Cancel", - "UpdateOriginal": "Update Original", - "Update": "Update", - "WarningOriginalWillBeUpdated": "The original version of this document will be updated.", - "OriginalHasModifications": "Original Has Modifications", - "Overwrite": "Overwrite", - "WarningOverwriteOriginalChanges": "Be careful, the original has changes not in this document. Those changes will be overwritten.", - "OriginalLooksUnrelated": "Original Looks Unrelated", - "WarningWillBeOverwritten": "It will be overwritten, losing any content not in this document.", - "OriginalLooksIdentical": "Original Looks Identical", - "WarningAlreadyIdentical": "However, it appears to be already identical.", - "SignUp": "Sign up", - "ToSaveSignUpAndReload": "To save your changes, please sign up, then reload this page.", - "NoDestinationWorkspace": "No destination workspace", + "Enter document name": "Enter document name", + "However, it appears to be already identical.": "However, it appears to be already identical.", + "Include the structure without any of the data.": "Include the structure without any of the data.", + "It will be overwritten, losing any content not in this document.": "It will be overwritten, losing any content not in this document.", "Name": "Name", - "EnterDocumentName": "Enter document name", - "AsTemplate": "As Template", - "IncludeStructureWithoutData": "Include the structure without any of the data.", + "No destination workspace": "No destination workspace", "Organization": "Organization", - "NoWriteAccessToSite": "You do not have write access to this site", + "Original Has Modifications": "Original Has Modifications", + "Original Looks Unrelated": "Original Looks Unrelated", + "Overwrite": "Overwrite", + "Replacing the original requires editing rights on the original document.": "Replacing the original requires editing rights on the original document.", + "Sign up": "Sign up", + "The original version of this document will be updated.": "The original version of this document will be updated.", + "To save your changes, please sign up, then reload this page.": "To save your changes, please sign up, then reload this page.", + "Update": "Update", + "Update Original": "Update Original", "Workspace": "Workspace", - "NoWriteAccessToWorkspace": "You do not have write access to the selected workspace" + "You do not have write access to the selected workspace": "You do not have write access to the selected workspace", + "You do not have write access to this site": "You do not have write access to this site" }, "NotifyUI": { - "UpgradePlan": "Upgrade Plan", - "Renew": "Renew", - "GoToPersonalSite": "Go to your free personal site", - "ErrorCannotFindPersonalSite": "Cannot find personal site, sorry!", - "ReportProblem": "Report a problem", - "AskForHelp": "Ask for help", + "Ask for help": "Ask for help", + "Cannot find personal site, sorry!": "Cannot find personal site, sorry!", + "Give feedback": "Give feedback", + "Go to your free personal site": "Go to your free personal site", + "No notifications": "No notifications", "Notifications": "Notifications", - "GiveFeedback": "Give feedback", - "NoNotifications": "No notifications" + "Renew": "Renew", + "Report a problem": "Report a problem", + "Upgrade Plan": "Upgrade Plan" }, "NTextBox": { "false": "false", @@ -365,426 +459,293 @@ "Finish": "Finish", "Next": "Next" }, - "WidgetTitle": { - "OverrideTitle": "Override widget title", - "DataTableName": "DATA TABLE NAME", - "NewTableName": "Provide a table name", - "WidgetTitle": "WIDGET TITLE", - "Save": "Save", - "Cancel": "Cancel" - }, - "WelcomeQuestions": { - "WelcomeToGrist": "Welcome to Grist!", - "ProductDevelopment": "Product Development", - "FinanceAccounting": "Finance & Accounting", - "MediaProduction": "Media Production", - "ITTechnology": "IT & Technology", - "Marketing": "Marketing", - "Research": "Research", - "Sales": "Sales", - "Education": "Education", - "HRManagement": "HR & Management", - "Other": "Other", - "WhatBringsYouToGrist": "What brings you to Grist? Please help us serve you better.", - "TypeHere": "Type here" - }, "OpenVideoTour": { - "YouTubeVideoPlayer": "YouTube video player", - "GristVideoTour": "Grist Video Tour", - "VideoTour": "Video Tour" - }, - "Pages": { - "TableWillNoLongerBeVisible_one": "The following table will no longer be visible", - "TableWillNoLongerBeVisible_other": "The following tables will no longer be visible", - "DeleteDataAndPage": "Delete data and this page.", - "Delete": "Delete" + "Grist Video Tour": "Grist Video Tour", + "Video Tour": "Video Tour", + "YouTube video player": "YouTube video player" }, "PageWidgetPicker": { - "BuildingWidget": "Building {{- label}} widget", - "SelectWidget": "Select Widget", - "SelectData": "Select Data", - "GroupBy": "Group by", - "AddToPage": "Add to Page" + "Add to Page": "Add to Page", + "Building {{- label}} widget": "Building {{- label}} widget", + "Group by": "Group by", + "Select Data": "Select Data", + "Select Widget": "Select Widget" + }, + "Pages": { + "Delete": "Delete", + "Delete data and this page.": "Delete data and this page.", + "The following tables will no longer be visible_one": "The following tables will no longer be visible", + "The following tables will no longer be visible_other": "The following tables will no longer be visible" + }, + "PermissionsWidget": { + "Allow All": "Allow All", + "Deny All": "Deny All", + "Read Only": "Read Only" + }, + "PluginScreen": { + "Import failed: ": "Import failed: " + }, + "RecordLayout": { + "Updating record layout.": "Updating record layout." + }, + "RecordLayoutEditor": { + "AddField": "AddField", + "Cancel": "Cancel", + "CreateNewField": "CreateNewField", + "SaveLayout": "SaveLayout", + "ShowField": "ShowField" + }, + "RefSelect": { + "Add Column": "Add Column", + "No columns to add": "No columns to add" }, "RightPanel": { - "Column_one": "Column", - "Column_other": "Columns", - "Field_one": "Field", - "Field_other": "Fields", + "CHART TYPE": "CHART TYPE", + "COLUMN TYPE": "COLUMN TYPE", + "CUSTOM": "CUSTOM", + "Change Widget": "Change Widget", + "Columns_one": "Columns", + "Columns_other": "Columns", + "DATA TABLE": "DATA TABLE", + "DATA TABLE NAME": "DATA TABLE NAME", + "Data": "Data", + "Detach": "Detach", + "Edit Data Selection": "Edit Data Selection", + "Fields_one": "Fields", + "Fields_other": "Fields", + "GROUPED BY": "GROUPED BY", + "ROW STYLE": "ROW STYLE", + "Row Style": "Row Style", + "SELECT BY": "SELECT BY", + "SELECTOR FOR": "SELECTOR FOR", + "SOURCE DATA": "SOURCE DATA", + "Save": "Save", + "Select Widget": "Select Widget", "Series_one": "Series", "Series_other": "Series", - "ColumnType": "COLUMN TYPE", - "Transform": "TRANSFORM", - "Widget": "Widget", - "SortAndFilter": "Sort & Filter", - "Data": "Data", - "DataTableName": "DATA TABLE NAME", - "WidgetTitle": "WIDGET TITLE", - "ChangeWidget": "Change Widget", + "Sort & Filter": "Sort & Filter", + "TRANSFORM": "TRANSFORM", "Theme": "Theme", - "RowStyleUpper": "ROW STYLE", - "RowStyle": "Row Style", - "ChartType": "CHART TYPE", - "Custom": "CUSTOM", - "Sort": "SORT", - "Filter": "FILTER", - "DataTable": "DATA TABLE", - "SourceData": "SOURCE DATA", - "GroupedBy": "GROUPED BY", - "EditDataSelection": "Edit Data Selection", - "Detach": "Detach", - "SelectBy": "SELECT BY", - "SelectWidget": "Select Widget", - "SelectorFor": "SELECTOR FOR", - "Save": "Save", - "NoEditAccess": "You do not have edit access to this document" + "WIDGET TITLE": "WIDGET TITLE", + "Widget": "Widget", + "You do not have edit access to this document": "You do not have edit access to this document" }, "RowContextMenu": { - "InsertRow": "Insert row", - "InsertRowAbove": "Insert row above", - "InsertRowBelow": "Insert row below", - "DuplicateRows_one": "Duplicate row", - "DuplicateRows_other": "Duplicate rows", + "Copy anchor link": "Copy anchor link", "Delete": "Delete", - "CopyAnchorLink": "Copy anchor link" + "Duplicate rows_one": "Duplicate rows", + "Duplicate rows_other": "Duplicate rows", + "Insert row": "Insert row", + "Insert row above": "Insert row above", + "Insert row below": "Insert row below" }, - "sendToDrive":{ - "SendingToGoogleDrive":"Sending file to Google Drive" + "SelectionSummary": { + "Copied to clipboard": "Copied to clipboard" }, - "ShareMenu":{ - "BackToCurrent": "Back to Current", - "SaveDocument":"Save Document", - "SaveCopy":"Save Copy", - "Unsaved":"Unsaved", - "DuplicateDocument":"Duplicate Document", - "ManageUsers":"Manage Users", - "AccessDetails":"Access Details", - "CurrentVersion":"Current Version", - "Original":"Original", - "ReturnToTermToUse":"Return to {{termToUse}}", - "ReplaceTermToUse":"Replace {{termToUse}}...", - "CompareTermToUse":"Compare to {{termToUse}}", - "WorkOnCopy":"Work on a Copy", - "EditWithoutAffecting":"Edit without affecting the original", - "ShowInFolder":"Show in folder", - "Download":"Download", - "ExportCSV":"Export CSV", - "ExportXLSX":"Export XLSX", - "SendToGoogleDrive":"Send to Google Drive" + "ShareMenu": { + "Access Details": "Access Details", + "Back to Current": "Back to Current", + "Compare to {{termToUse}}": "Compare to {{termToUse}}", + "Current Version": "Current Version", + "Download": "Download", + "Duplicate Document": "Duplicate Document", + "Edit without affecting the original": "Edit without affecting the original", + "Export CSV": "Export CSV", + "Export XLSX": "Export XLSX", + "Manage Users": "Manage Users", + "Original": "Original", + "Replace {{termToUse}}...": "Replace {{termToUse}}...", + "Return to {{termToUse}}": "Return to {{termToUse}}", + "Save Copy": "Save Copy", + "Save Document": "Save Document", + "Send to Google Drive": "Send to Google Drive", + "Show in folder": "Show in folder", + "Unsaved": "Unsaved", + "Work on a Copy": "Work on a Copy" }, - "SiteSwitcher":{ - "SwitchSites":"Switch Sites", - "CreateNewTeamSite":"Create new team site" + "SiteSwitcher": { + "Create new team site": "Create new team site", + "Switch Sites": "Switch Sites" }, - "SortConfig":{ - "AddColumn": "Add Column", - "UpdateData": "Update Data", - "UseChoicePosition": "Use choice position", - "NaturalSort": "Natural sort", - "EmptyValuesLast": "Empty values last" + "SortConfig": { + "Add Column": "Add Column", + "Empty values last": "Empty values last", + "Natural sort": "Natural sort", + "Update Data": "Update Data", + "Use choice position": "Use choice position" }, - "SortFilterConfig":{ - "Save": "Save", + "SortFilterConfig": { + "Filter": "Filter", "Revert": "Revert", - "Sort": "SORT", - "Filter": "FILTER", - "UpdateSortFilterSettings": "Update Sort & Filter settings" + "Save": "Save", + "Sort": "Sort", + "UpdateSortFilterSettings": "UpdateSortFilterSettings" }, "ThemeConfig": { - "Appearance": "Appearance ", - "SyncWithOS": "Switch appearance automatically to match system" + "Appearance ": "Appearance ", + "Switch appearance automatically to match system": "Switch appearance automatically to match system" }, "Tools": { - "Tools": "TOOLS", - "AccessRules": "Access Rules", - "Data": "Raw Data", - "DocumentHistory": "Document History", - "ValidateData": "Validate Data", - "CodeView": "Code View", - "HowToTutorial": "How-to Tutorial", - "DocumentTour": "Tour of this Document", - "DeleteDocumentTour": "Delete document tour?", + "Access Rules": "Access Rules", + "Code View": "Code View", "Delete": "Delete", - "ViewingAsYourself": "Return to viewing as yourself", - "RawData": "Raw Data" + "Delete document tour?": "Delete document tour?", + "Document History": "Document History", + "How-to Tutorial": "How-to Tutorial", + "Raw Data": "Raw Data", + "Return to viewing as yourself": "Return to viewing as yourself", + "TOOLS": "TOOLS", + "Tour of this Document": "Tour of this Document", + "Validate Data": "Validate Data" }, "TopBar": { - "ManageTeam": "Manage Team" + "Manage Team": "Manage Team" }, "TriggerFormulas": { - "AnyField": "Any field", - "NewRecords": "Apply to new records", - "ChangesTo": "Apply on changes to:", - "RecordChanges": "Apply on record changes", - "CurrentField": "Current field ", - "DataCleaning": "(data cleaning)", - "ExceptFormulas": "(except formulas)", - "OK": "OK", + "Any field": "Any field", + "Apply on changes to:": "Apply on changes to:", + "Apply on record changes": "Apply on record changes", + "Apply to new records": "Apply to new records", "Cancel": "Cancel", - "Close": "Close" + "Close": "Close", + "Current field ": "Current field ", + "OK": "OK" }, - "VisibleFieldsConfig": { - "NoReorderHiddenField": "Hidden Fields cannot be reordered", - "NoDropInHiddenField": "Cannot drop items into Hidden Fields", - "SelectAll": "Select All", - "Clear": "Clear" + "TypeTransform": { + "Apply": "Apply", + "Cancel": "Cancel", + "Preview": "Preview", + "Revise": "Revise", + "UpdateFormula": "UpdateFormula" + }, + "UserManagerModel": { + "Editor": "Editor", + "In Full": "In Full", + "No Default Access": "No Default Access", + "None": "None", + "Owner": "Owner", + "View & Edit": "View & Edit", + "View Only": "View Only", + "Viewer": "Viewer" + }, + "ValidationPanel": { + "Rule {{length}}": "Rule {{length}}", + "Update formula (Shift+Enter)": "Update formula (Shift+Enter)" + }, + "ViewAsBanner": { + "UnknownUser": "Unknown User" + }, + "ViewConfigTab": { + "Advanced settings": "Advanced settings", + "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.": "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.", + "Blocks": "Blocks", + "Compact": "Compact", + "Edit Card Layout": "Edit Card Layout", + "Form": "Form", + "Make On-Demand": "Make On-Demand", + "Plugin: ": "Plugin: ", + "Section: ": "Section: ", + "Unmark On-Demand": "Unmark On-Demand" }, "ViewLayoutMenu": { - "DeleteRecord": "Delete record", - "CopyAnchorLink": "Copy anchor link", - "ShowRawData": "Show raw data", - "PrintWidget": "Print widget", - "DownloadCSV": "Download as CSV", - "DownloadXLSX": "Download as XLSX", - "EditCardLayout": "Edit Card Layout", - "WidgetOptions": "Widget options", - "AdvancedSortFilter": "Advanced Sort & Filter", - "DataSelection": "Data selection", - "OpenConfiguration": "Open configuration", - "DeleteWidget": "Delete widget" + "Advanced Sort & Filter": "Advanced Sort & Filter", + "Copy anchor link": "Copy anchor link", + "Data selection": "Data selection", + "Delete record": "Delete record", + "Delete widget": "Delete widget", + "Download as CSV": "Download as CSV", + "Download as XLSX": "Download as XLSX", + "Edit Card Layout": "Edit Card Layout", + "Open configuration": "Open configuration", + "Print widget": "Print widget", + "Show raw data": "Show raw data", + "Widget options": "Widget options" }, "ViewSectionMenu": { - "UpdateSortFilterSettings": "Update Sort&Filter settings", - "Save": "Save", + "(customized)": "(customized)", + "(empty)": "(empty)", + "(modified)": "(modified)", + "Custom options": "Custom options", + "FILTER": "FILTER", "Revert": "Revert", - "SortedBy": "Sorted by", - "AddFilter": "Add Filter", - "ToggleFilterBar": "Toggle Filter Bar", - "FilteredBy": "Filtered by", - "Customized":"(customized)", - "Modified":"(modified)", - "Empty":"(empty)", - "CustomOptions":"Custom options", - "Sort": "SORT", - "Filter": "FILTER" + "SORT": "SORT", + "Save": "Save", + "Update Sort&Filter settings": "Update Sort&Filter settings" }, - "aclui": { - "AccessRules": { - "Checking": "Checking...", - "Saved": "Saved", - "Invalid": "Invalid", - "Save": "Save", - "Reset": "Reset", - "AddTableRules": "Add Table Rules", - "AddUserAttributes": "Add User Attributes", - "UserAttributes": "User Attributes", - "AttributeToLookUp": "Attribute to Look Up", - "LookupTable": "Lookup Table", - "LookupColumn": "Lookup Column", - "DefaultRules": "Default Rules", - "Condition": "Condition", - "Permissions": "Permissions", - "RulesForTable": "Rules for table ", - "AddColumnRule": "Add Column Rule", - "AddDefaultRule": "Add Default Rule", - "DeleteTableRules": "Delete Table Rules", - "SpecialRules": "Special Rules", - "AccessRulesDescription": "Allow everyone to view Access Rules.", - "FullCopiesDescription": "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.", - "AccessRulesName": "Permission to view Access Rules", - "FullCopies": "Permission to access the document in full when needed", - "AttributeNamePlaceholder": "Attribute name", - "Everyone": "Everyone", - "EveryoneElse": "Everyone Else", - "EnterCondition": "Enter Condition", - "RemoveRulesMentioningTable": "Remove {{- tableId }} rules", - "RemoveRulesMentioningColumn": "Remove column {{- colId }} from {{- tableId }} rules", - "RemoveUserAttribute": "Remove {{- name }} user attribute", - "MemoEditorPlaceholder": "Type a message...", - "ViewAs": "View As" - }, - "ViewAsDropdown": { - "ViewAs": "View As", - "UsersFrom": "Users from table", - "ExampleUsers": "Example Users" - }, - "PermissionsWidget": { - "AllowAll": "Allow All", - "DenyAll": "Deny All", - "ReadOnly": "Read Only" - } + "VisibleFieldsConfig": { + "Cannot drop items into Hidden Fields": "Cannot drop items into Hidden Fields", + "Clear": "Clear", + "Hidden Fields cannot be reordered": "Hidden Fields cannot be reordered", + "Select All": "Select All" }, - "lib": { - "ACUserManager": { - "InviteNewMember": "Invite new member", - "EmailInputPlaceholder": "Enter email address", - "InviteEmail": "We'll email an invite to {{email}}" - } + "WelcomeQuestions": { + "Other": "Other", + "Type here": "Type here", + "Welcome to Grist!": "Welcome to Grist!", + "What brings you to Grist? Please help us serve you better.": "What brings you to Grist? Please help us serve you better." }, - "models": { - "AppModel": { - "TeamSiteSuspended": "This team site is suspended. Documents can be read, but not modified." - }, - "DocPageModel": { - "ErrorAccessingDocument": "Error accessing document", - "Reload": "Reload", - "ReloadingOrRecoveryMode": "You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]", - "AccessError_denied": "Sorry, access to this document has been denied. [{{error}}]", - "AccessError_recover": "Document owners can attempt to recover the document. [{{error}}]", - "EnterRecoveryMode": "Enter recovery mode", - "AddPage": "Add Page", - "AddWidgetToPage": "Add Widget to Page", - "AddEmptyTable": "Add Empty Table", - "NoEditAccess": "You do not have edit access to this document" - }, - "UserManagerModel": { - "Owner": "Owner", - "Editor": "Editor", - "Viewer": "Viewer", - "NoDefaultAccess": "No Default Access", - "InFull": "In Full", - "ViewAndEdit": "View & Edit", - "ViewOnly": "View Only", - "None": "None" - } + "WidgetTitle": { + "Cancel": "Cancel", + "DATA TABLE NAME": "DATA TABLE NAME", + "Override widget title": "Override widget title", + "Provide a table name": "Provide a table name", + "Save": "Save", + "WIDGET TITLE": "WIDGET TITLE" }, - "ui2018": { - "breadcrumbs": { - "FiddleExplanation": "You may make edits, but they will create a new copy and will\nnot affect the original document.", - "Snapshot": "snapshot", - "Unsaved": "unsaved", - "RecoveryMode": "recovery mode", - "Override": "override", - "Fiddle": "fiddle" - }, - "ColorSelect": { - "DefaultCellStyle": "Default cell style", - "Apply": "Apply", - "Cancel": "Cancel" - }, - "menus": { - "SelectFields": "Select fields", - "WorkspacesAvailableOnTeamPlans": "* Workspaces are available on team plans. ", - "UpgradeNow": "Upgrade now" - }, - "modals": { - "Save": "Save", - "Cancel": "Cancel", - "Ok": "Ok" - }, - "pages": { - "Rename": "Rename", - "Remove": "Remove", - "DuplicatePage": "Duplicate Page", - "NoEditAccess": "You do not have edit access to this document" - }, - "search": { - "SearchInDocument": "Search in document", - "NoResults": "No results", - "FindNext": "Find Next ", - "FindPrevious": "Find Previous " - } + "breadcrumbs": { + "You may make edits, but they will create a new copy and will\nnot affect the original document.": "You may make edits, but they will create a new copy and will\nnot affect the original document.", + "fiddle": "fiddle", + "override": "override", + "recovery mode": "recovery mode", + "snapshot": "snapshot", + "unsaved": "unsaved" }, - "components": { - "ActionLog": { - "ActionLogFailed":"Action Log failed to load", - "TableRemovedInAction":"Table {{tableId}} was subsequently removed in action #{{actionNum}}", - "RowRemovedInAction":"This row was subsequently removed in action {{action.actionNum}}", - "ColumnRemovedInAction":"Column {{colId}} was subsequently removed in action #{{action.actionNum}}" - }, - "ChartView": { - "EachYFollowedByOne":"Each Y series is followed by a series for the length of error bars.", - "EachYFollowedByTwo":"Each Y series is followed by two series, for top and bottom error bars.", - "CreateSeparateSeries":"Create separate series for each value of the selected column.", - "PickColumn":"Pick a column", - "SelectedNewGroupDataColumns":"selected new group data columns", - "ToggleChartAggregation":"Toggle chart aggregation" - }, - "CodeEditorPanel": { - "AccessDenied":"Access denied", - "CodeViewOnlyFullAccess":"Code View is available only when you have full document access." - }, - "DataTables": { - "RawDataTables":"Raw Data Tables", - "ClickToCopy":"Click to copy", - "TableIDCopied":"Table ID copied to clipboard", - "DuplicateTable":"Duplicate Table", - "NoEditAccess":"You do not have edit access to this document", - "DeleteData":"Delete {{formattedTableName}} data, and remove it from all pages?" - }, - "DocumentUsage": { - "UsageStatisticsOnlyFullAccess":"Usage statistics are only available to users with full access to the document data.", - "TotalSize":"The total size of all data in this document, excluding attachments.", - "Updates":"Updates every 5 minutes.", - "AttachmentsSize": "Attachments Size", - "DataSize": "Data Size", - "Usage": "Usage", - "LimitContactSiteOwner": "Contact the site owner to upgrade the plan to raise limits.", - "UpgradeLinkText": "start your 30-day free trial of the Pro plan.", - "ForHigherLimits": "For higher limits, ", - "StatusMessageApproachingLimit": "This document is {{- link}} free plan limits.", - "StatusMessageGracePeriod": "Document limits {{- link}}.", - "StatusMessageGracePeriodElse": "Document limits {{- link}}. In {{gracePeriodDays}} days, this document will be read-only.", - "StatusMessageDeleteOnly": "This document {{- link}} free plan limits and is now read-only, but you can delete rows.", - "Rows": "Rows" - }, - "ViewConfigTab": { - "UnmarkOnDemandTitle": "Unmark table On-Demand?", - "UnmarkOnDemandButton": "Unmark On-Demand", - "UnmarkOnDemandText": "If you unmark table {{- table}}' as On-Demand, its data will be loaded into the calculation engine and will be available for use in formulas. For a big table, this may greatly increase load times.{{- br}}{{-br}}Changing this setting will reload the document for all users.", - "MakeOnDemandTitle": "Make table On-Demand?", - "MakeOnDemandButton": "Make On-Demand", - "MakeOnDemandText": "If you make table {{table}} On-Demand, its data will no longer be loaded into the calculation engine and will not be available for use in formulas. It will remain available for viewing and editing.", - "AdvancedSettings": "Advanced settings", - "BigTablesMayBeMarked": "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.", - "Form": "Form", - "Compact": "Compact", - "Blocks": "Blocks", - "EditCardLayout": "Edit Card Layout", - "PluginColon": "Plugin: ", - "SectionColon": "Section: " - }, - "Drafts": { - "UndoDiscard":"Undo discard", - "RestoreLastEdit":"Restore last edit" - }, - "duplicatePage": { - "DoesNotCopyData":"Note that this does not copy data, but creates another view of the same data.", - "DuplicatePageName":"Duplicate page {{pageName}}" - }, - "GristDoc": { - "ImportFromFile":"Import from file", - "AddedNewLinkedSection":"Added new linked section to view {{viewName}}", - "SavedLinkedSectionIn":"Saved linked section {{title}} in view {{name}}" - }, - "Importer": { - "UpdateExistingRecords":"Update existing records", - "MergeRowsThatMatch":"Merge rows that match these fields:", - "SelectFieldsToMatch":"Select fields to match on" - }, - "PluginScreen": { - "ImportFailed":"Import failed: " - }, - "RecordLayout": { - "UpdatingRecordLayout":"Updating record layout." - }, - "RecordLayoutEditor": { - "AddField":"Add Field", - "CreateNewField":"Create New Field", - "ShowField":"Show field {{- label}}", - "SaveLayout":"Save Layout", - "Cancel":"Cancel" - }, - "RefSelect": { - "AddColumn":"Add Column", - "NoColumnsAdd":"No columns to add" - }, - "SelectionSummary": { - "CopiedClipboard":"Copied to clipboard" - }, - "TypeTransformation": { - "Cancel":"Cancel", - "Preview":"Preview", - "UpdateFormula":"Update formula (Shift+Enter)", - "Revise":"Revise", - "Apply":"Apply" - }, - "ValidationPanel": { - "RuleLength":"Rule {{length}}", - "UpdateFormula":"Update formula (Shift+Enter)" - }, - "ViewAsBanner": { - "UnknownUser": "Unknown User" - } + "duplicatePage": { + "Duplicate page {{pageName}}": "Duplicate page {{pageName}}", + "Note that this does not copy data, but creates another view of the same data.": "Note that this does not copy data, but creates another view of the same data." + }, + "errorPages": { + "Access denied{{suffix}}": "Access denied{{suffix}}", + "Add account": "Add account", + "Contact support": "Contact support", + "Error{{suffix}}": "Error{{suffix}}", + "Go to main page": "Go to main page", + "Page not found{{suffix}}": "Page not found{{suffix}}", + "Sign in": "Sign in", + "Sign in again": "Sign in again", + "Sign in to access this organization's documents.": "Sign in to access this organization's documents.", + "Signed out{{suffix}}": "Signed out{{suffix}}", + "Something went wrong": "Something went wrong", + "The requested page could not be found.{{separator}}Please check the URL and try again.": "The requested page could not be found.{{separator}}Please check the URL and try again.", + "There was an error: {{message}}": "There was an error: {{message}}", + "There was an unknown error.": "There was an unknown error.", + "You are now signed out.": "You are now signed out.", + "You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.": "You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.", + "You do not have access to this organization's documents.": "You do not have access to this organization's documents." + }, + "menus": { + "* Workspaces are available on team plans. ": "* Workspaces are available on team plans. ", + "Select fields": "Select fields", + "Upgrade now": "Upgrade now" + }, + "modals": { + "Cancel": "Cancel", + "Ok": "Ok", + "Save": "Save" + }, + "pages": { + "Duplicate Page": "Duplicate Page", + "Remove": "Remove", + "Rename": "Rename", + "You do not have edit access to this document": "You do not have edit access to this document" + }, + "search": { + "Find Next ": "Find Next ", + "Find Previous ": "Find Previous ", + "No results": "No results", + "Search in document": "Search in document" + }, + "sendToDrive": { + "Sending file to Google Drive": "Sending file to Google Drive" } -} +} \ No newline at end of file From 676c27d6c9186b0b8982a328a07a07cc61647742 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Fri, 9 Dec 2022 16:46:03 +0100 Subject: [PATCH 11/17] Remove prefix from translations keys --- app/client/aclui/AccessRules.ts | 2 +- app/client/aclui/PermissionsWidget.ts | 2 +- app/client/components/ActionLog.ts | 2 +- app/client/components/ChartView.ts | 2 +- app/client/components/CodeEditorPanel.ts | 2 +- app/client/components/DataTables.ts | 2 +- app/client/components/DocumentUsage.ts | 2 +- app/client/components/GristDoc.ts | 2 +- app/client/components/Importer.ts | 2 +- app/client/components/PluginScreen.ts | 2 +- app/client/components/RecordLayout.js | 2 +- app/client/components/RecordLayoutEditor.js | 2 +- app/client/components/RefSelect.ts | 2 +- app/client/components/SelectionSummary.ts | 2 +- app/client/components/TypeTransform.ts | 2 +- app/client/components/ValidationPanel.js | 2 +- app/client/components/ViewConfigTab.js | 2 +- app/client/components/duplicatePage.ts | 2 +- app/client/lib/ACUserManager.ts | 2 +- app/client/models/AppModel.ts | 2 +- app/client/models/DocPageModel.ts | 2 +- app/client/models/UserManagerModel.ts | 2 +- app/client/ui2018/ColorSelect.ts | 2 +- app/client/ui2018/breadcrumbs.ts | 2 +- app/client/ui2018/menus.ts | 2 +- app/client/ui2018/modals.ts | 2 +- app/client/ui2018/pages.ts | 2 +- app/client/ui2018/search.ts | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index 7f49a2d2..8a558d78 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -55,7 +55,7 @@ import { import {makeT} from 'app/client/lib/localization'; import isEqual = require('lodash/isEqual'); -const t = makeT('aclui.AccessRules'); +const t = makeT('AccessRules'); // tslint:disable:max-classes-per-file no-console diff --git a/app/client/aclui/PermissionsWidget.ts b/app/client/aclui/PermissionsWidget.ts index e121302f..5d3a467b 100644 --- a/app/client/aclui/PermissionsWidget.ts +++ b/app/client/aclui/PermissionsWidget.ts @@ -15,7 +15,7 @@ import {makeT} from 'app/client/lib/localization'; // One of the strings 'read', 'update', etc. export type PermissionKey = keyof PartialPermissionSet; -const t = makeT('aclui.PermissionsWidget'); +const t = makeT('PermissionsWidget'); /** * Renders a box for each of availableBits, and a dropdown with a description and some shortcuts. diff --git a/app/client/components/ActionLog.ts b/app/client/components/ActionLog.ts index 4ea80f3e..aa0bdb9d 100644 --- a/app/client/components/ActionLog.ts +++ b/app/client/components/ActionLog.ts @@ -47,7 +47,7 @@ const state = { DEFAULT: 'default' }; -const t = makeT('components.ActionLog'); +const t = makeT('ActionLog'); export class ActionLog extends dispose.Disposable implements IDomComponent { diff --git a/app/client/components/ChartView.ts b/app/client/components/ChartView.ts index 1d267ce9..0f025002 100644 --- a/app/client/components/ChartView.ts +++ b/app/client/components/ChartView.ts @@ -50,7 +50,7 @@ const DONUT_DEFAULT_TEXT_SIZE = 24; const testId = makeTestId('test-chart-'); -const t = makeT('components.ChartView'); +const t = makeT('ChartView'); function isPieLike(chartType: string) { return ['pie', 'donut'].includes(chartType); diff --git a/app/client/components/CodeEditorPanel.ts b/app/client/components/CodeEditorPanel.ts index 99363842..19167a74 100644 --- a/app/client/components/CodeEditorPanel.ts +++ b/app/client/components/CodeEditorPanel.ts @@ -9,7 +9,7 @@ import {makeT} from 'app/client/lib/localization'; const hljs = require('highlight.js/lib/core'); hljs.registerLanguage('python', require('highlight.js/lib/languages/python')); -const t = makeT('components.CodeEditorPanel'); +const t = makeT('CodeEditorPanel'); export class CodeEditorPanel extends DisposableWithEvents { private _schema = Observable.create(this, ''); diff --git a/app/client/components/DataTables.ts b/app/client/components/DataTables.ts index 06bcd75a..9f169b6a 100644 --- a/app/client/components/DataTables.ts +++ b/app/client/components/DataTables.ts @@ -16,7 +16,7 @@ import {makeT} from 'app/client/lib/localization'; const testId = makeTestId('test-raw-data-'); -const t = makeT('components.DataTables'); +const t = makeT('DataTables'); export class DataTables extends Disposable { private _tables: Observable; diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 914ef0ba..54cb1d9f 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -13,7 +13,7 @@ import {canUpgradeOrg} from 'app/common/roles'; import {Computed, Disposable, dom, DomContents, DomElementArg, makeTestId, styled} from 'grainjs'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('components.DocumentUsage'); +const t = makeT('DocumentUsage'); const testId = makeTestId('test-doc-usage-'); diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index c5c8f46e..13a11ec6 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -87,7 +87,7 @@ import * as ko from 'knockout'; import cloneDeepWith = require('lodash/cloneDeepWith'); import isEqual = require('lodash/isEqual'); -const t = makeT('components.GristDoc'); +const t = makeT('GristDoc'); const G = getBrowserGlobals('document', 'window'); diff --git a/app/client/components/Importer.ts b/app/client/components/Importer.ts index c9fdf8e9..def7e146 100644 --- a/app/client/components/Importer.ts +++ b/app/client/components/Importer.ts @@ -36,7 +36,7 @@ import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox'; import {ACCESS_DENIED, AUTH_INTERRUPTED, canReadPrivateFiles, getGoogleCodeForReading} from 'app/client/ui/googleAuth'; import debounce = require('lodash/debounce'); -const t = makeT('components.Importer'); +const t = makeT('Importer'); // We expect a function for creating the preview GridView, to avoid the need to require the diff --git a/app/client/components/PluginScreen.ts b/app/client/components/PluginScreen.ts index 3eb9cb88..1e434287 100644 --- a/app/client/components/PluginScreen.ts +++ b/app/client/components/PluginScreen.ts @@ -7,7 +7,7 @@ import { PluginInstance } from 'app/common/PluginInstance'; import { RenderTarget } from 'app/plugin/RenderOptions'; import { Disposable, dom, DomContents, Observable, styled } from 'grainjs'; -const t = makeT('components.PluginScreen'); +const t = makeT('PluginScreen'); /** * Rendering options for the PluginScreen modal. diff --git a/app/client/components/RecordLayout.js b/app/client/components/RecordLayout.js index dfa48e3a..6dd6f582 100644 --- a/app/client/components/RecordLayout.js +++ b/app/client/components/RecordLayout.js @@ -41,7 +41,7 @@ var {menu} = require('../ui2018/menus'); var {testId} = require('app/client/ui2018/cssVars'); var {contextMenu} = require('app/client/ui/contextMenu'); -const t = makeT('components.RecordLayout'); +const t = makeT('RecordLayout'); /** * Construct a RecordLayout. diff --git a/app/client/components/RecordLayoutEditor.js b/app/client/components/RecordLayoutEditor.js index 79955c1d..e91aff4c 100644 --- a/app/client/components/RecordLayoutEditor.js +++ b/app/client/components/RecordLayoutEditor.js @@ -6,7 +6,7 @@ var {makeT} = require('app/client/lib/localization'); var commands = require('./commands'); var LayoutEditor = require('./LayoutEditor'); -const t = makeT('components.RecordLayoutEditor'); +const t = makeT('RecordLayoutEditor'); const {basicButton, cssButton, primaryButton} = require('app/client/ui2018/buttons'); const {icon} = require('app/client/ui2018/icons'); const {menu, menuDivider, menuItem} = require('app/client/ui2018/menus'); diff --git a/app/client/components/RefSelect.ts b/app/client/components/RefSelect.ts index e45377aa..a751bd56 100644 --- a/app/client/components/RefSelect.ts +++ b/app/client/components/RefSelect.ts @@ -14,7 +14,7 @@ import ko from 'knockout'; import {menu, menuItem} from 'popweasel'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('components.RefSelect'); +const t = makeT('RefSelect'); interface Item { label: string; diff --git a/app/client/components/SelectionSummary.ts b/app/client/components/SelectionSummary.ts index 1e3d2bbe..15610e6c 100644 --- a/app/client/components/SelectionSummary.ts +++ b/app/client/components/SelectionSummary.ts @@ -16,7 +16,7 @@ import ko from 'knockout'; import {Computed, Disposable, dom, makeTestId, Observable, styled, subscribe} from 'grainjs'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('components.SelectionSummary'); +const t = makeT('SelectionSummary'); /** * A beginning and end index for a range of columns or rows. diff --git a/app/client/components/TypeTransform.ts b/app/client/components/TypeTransform.ts index 509e8890..4741e8c8 100644 --- a/app/client/components/TypeTransform.ts +++ b/app/client/components/TypeTransform.ts @@ -19,7 +19,7 @@ import {UserAction} from 'app/common/DocActions'; import {Computed, dom, fromKo, Observable} from 'grainjs'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('components.TypeTransformation'); +const t = makeT('TypeTransformation'); // To simplify diff (avoid rearranging methods to satisfy private/public order). /* eslint-disable @typescript-eslint/member-ordering */ diff --git a/app/client/components/ValidationPanel.js b/app/client/components/ValidationPanel.js index e9f155a7..c7b1dbee 100644 --- a/app/client/components/ValidationPanel.js +++ b/app/client/components/ValidationPanel.js @@ -7,7 +7,7 @@ var kf = require('../lib/koForm'); var AceEditor = require('./AceEditor'); var {makeT} = require('app/client/lib/localization'); -const t = makeT('components.ValidationPanel'); +const t = makeT('ValidationPanel'); /** * Document level configuration settings. diff --git a/app/client/components/ViewConfigTab.js b/app/client/components/ViewConfigTab.js index 6510f11d..9202f636 100644 --- a/app/client/components/ViewConfigTab.js +++ b/app/client/components/ViewConfigTab.js @@ -19,7 +19,7 @@ const {makeT} = require('app/client/lib/localization'); const testId = makeTestId('test-vconfigtab-'); -const t = makeT('components.ViewConfigTab'); +const t = makeT('ViewConfigTab'); /** * Helper class that combines one ViewSection's data for building dom. diff --git a/app/client/components/duplicatePage.ts b/app/client/components/duplicatePage.ts index a4e7bca9..393f9e1f 100644 --- a/app/client/components/duplicatePage.ts +++ b/app/client/components/duplicatePage.ts @@ -15,7 +15,7 @@ import zip = require('lodash/zip'); import zipObject = require('lodash/zipObject'); import {makeT} from 'app/client/lib/localization'; -const t = makeT('components.duplicatePage'); +const t = makeT('duplicatePage'); // Duplicate page with pageId. Starts by prompting user for a new name. export async function duplicatePage(gristDoc: GristDoc, pageId: number) { diff --git a/app/client/lib/ACUserManager.ts b/app/client/lib/ACUserManager.ts index 66cc095e..3ce4e318 100644 --- a/app/client/lib/ACUserManager.ts +++ b/app/client/lib/ACUserManager.ts @@ -18,7 +18,7 @@ import {createUserImage, cssUserImage} from "app/client/ui/UserImage"; import {Computed, computed, dom, DomElementArg, Holder, IDisposableOwner, Observable, styled} from "grainjs"; import {cssMenuItem} from "popweasel"; -const t = makeT('lib.ACUserManager'); +const t = makeT('ACUserManager'); export interface ACUserItem extends ACItem { value: string; diff --git a/app/client/models/AppModel.ts b/app/client/models/AppModel.ts index 5a0b3849..4defcde7 100644 --- a/app/client/models/AppModel.ts +++ b/app/client/models/AppModel.ts @@ -24,7 +24,7 @@ import {getOrgName, Organization, OrgError, UserAPI, UserAPIImpl} from 'app/comm import {getUserPrefObs, getUserPrefsObs} from 'app/client/models/UserPrefs'; import {bundleChanges, Computed, Disposable, Observable, subscribe} from 'grainjs'; -const t = makeT('models.AppModel'); +const t = makeT('AppModel'); // Reexported for convenience. export {reportError} from 'app/client/models/errors'; diff --git a/app/client/models/DocPageModel.ts b/app/client/models/DocPageModel.ts index 6db13d20..0b15ec72 100644 --- a/app/client/models/DocPageModel.ts +++ b/app/client/models/DocPageModel.ts @@ -29,7 +29,7 @@ import {makeT} from 'app/client/lib/localization'; // tslint:disable:no-console -const t = makeT('models.DocPageModel'); +const t = makeT('DocPageModel'); export interface DocInfo extends Document { isReadonly: boolean; diff --git a/app/client/models/UserManagerModel.ts b/app/client/models/UserManagerModel.ts index 9e648be8..2e6d4096 100644 --- a/app/client/models/UserManagerModel.ts +++ b/app/client/models/UserManagerModel.ts @@ -12,7 +12,7 @@ import {ANONYMOUS_USER_EMAIL, Document, EVERYONE_EMAIL, FullUser, getRealAccess, import {computed, Computed, Disposable, obsArray, ObsArray, observable, Observable} from 'grainjs'; import some = require('lodash/some'); -const t = makeT('models.UserManagerModel'); +const t = makeT('UserManagerModel'); export interface UserManagerModel { initData: PermissionData; // PermissionData used to initialize the UserManager diff --git a/app/client/ui2018/ColorSelect.ts b/app/client/ui2018/ColorSelect.ts index b6d41d97..a473fed7 100644 --- a/app/client/ui2018/ColorSelect.ts +++ b/app/client/ui2018/ColorSelect.ts @@ -10,7 +10,7 @@ import {BindableValue, Computed, Disposable, dom, Observable, onKeyDown, styled} import {defaultMenuOptions, IOpenController, setPopupToCreateDom} from 'popweasel'; import {makeT} from 'app/client/lib/localization'; -const t = makeT('ui2018.ColorSelect'); +const t = makeT('ColorSelect'); export interface StyleOptions { textColor: ColorOption, diff --git a/app/client/ui2018/breadcrumbs.ts b/app/client/ui2018/breadcrumbs.ts index cb13f67a..16723452 100644 --- a/app/client/ui2018/breadcrumbs.ts +++ b/app/client/ui2018/breadcrumbs.ts @@ -14,7 +14,7 @@ import { cssLink } from 'app/client/ui2018/links'; import { BindableValue, dom, Observable, styled } from 'grainjs'; import { tooltip } from 'popweasel'; -const t = makeT('ui2018.breadcrumbs'); +const t = makeT('breadcrumbs'); export const cssBreadcrumbs = styled('div', ` color: ${theme.lightText}; diff --git a/app/client/ui2018/menus.ts b/app/client/ui2018/menus.ts index e8a19dab..93fe952f 100644 --- a/app/client/ui2018/menus.ts +++ b/app/client/ui2018/menus.ts @@ -11,7 +11,7 @@ import { BindableValue, Computed, dom, DomElementArg, DomElementMethod, IDomArgs MaybeObsArray, MutableObsArray, Observable, styled } from 'grainjs'; import * as weasel from 'popweasel'; -const t = makeT('ui2018.menus'); +const t = makeT('menus'); export interface IOptionFull { value: T; diff --git a/app/client/ui2018/modals.ts b/app/client/ui2018/modals.ts index a126d867..a6b365d1 100644 --- a/app/client/ui2018/modals.ts +++ b/app/client/ui2018/modals.ts @@ -12,7 +12,7 @@ import {Computed, Disposable, dom, DomContents, DomElementArg, input, keyframes, MultiHolder, Observable, styled} from 'grainjs'; import {cssMenuElem} from 'app/client/ui2018/menus'; -const t = makeT('ui2018.modals'); +const t = makeT('modals'); // IModalControl is passed into the function creating the body of the modal. export interface IModalControl { diff --git a/app/client/ui2018/pages.ts b/app/client/ui2018/pages.ts index 2bae3e61..5a7cef71 100644 --- a/app/client/ui2018/pages.ts +++ b/app/client/ui2018/pages.ts @@ -8,7 +8,7 @@ import { hoverTooltip } from 'app/client/ui/tooltips'; import { menu, menuItem, menuText } from "app/client/ui2018/menus"; import { dom, domComputed, DomElementArg, makeTestId, observable, Observable, styled } from "grainjs"; -const t = makeT('ui2018.pages'); +const t = makeT('pages'); const testId = makeTestId('test-docpage-'); diff --git a/app/client/ui2018/search.ts b/app/client/ui2018/search.ts index f2d9f1f6..acb72723 100644 --- a/app/client/ui2018/search.ts +++ b/app/client/ui2018/search.ts @@ -17,7 +17,7 @@ import debounce = require('lodash/debounce'); export * from 'app/client/models/SearchModel'; -const t = makeT('ui2018.search'); +const t = makeT('search'); const EXPAND_TIME = .5; From 83ea6868cbc8ec18ba2bc777b9697537d561690b Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 13 Dec 2022 17:26:42 +0100 Subject: [PATCH 12/17] Fix tests --- app/client/components/TypeTransform.ts | 2 +- app/client/ui/GridViewMenus.ts | 14 +++++----- app/client/ui/SortFilterConfig.ts | 2 +- static/locales/en.client.json | 36 +++++++++++--------------- test/nbrowser/Localization.ts | 2 +- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/app/client/components/TypeTransform.ts b/app/client/components/TypeTransform.ts index 4741e8c8..d2cd7883 100644 --- a/app/client/components/TypeTransform.ts +++ b/app/client/components/TypeTransform.ts @@ -72,7 +72,7 @@ export class TypeTransform extends ColumnTransform { return basicButton(dom.on('click', () => this.editor.writeObservable()), t('Preview'), testId("type-transform-update"), dom.cls('disabled', (use) => use(disableButtons) || use(this.formulaUpToDate)), - { title: t('UpdateFormula') } + { title: t('Update formula (Shift+Enter)') } ); } else { return basicButton(dom.on('click', () => { this._reviseTypeChange.set(true); }), diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts index 7e795ba8..fa9d0a77 100644 --- a/app/client/ui/GridViewMenus.ts +++ b/app/client/ui/GridViewMenus.ts @@ -135,10 +135,10 @@ export function MultiColumnMenu(options: IMultiColumnContextMenu) { const disableForReadonlyView = dom.cls('disabled', options.isReadonly); const num: number = options.numColumns; const nameClearColumns = options.isFiltered ? - t('ResetEntireColumns', {count: num}) : - t('ResetColumns', {count: num}); - const nameDeleteColumns = t('DeleteColumns', {count: num}); - const nameHideColumns = t('HideColumns', {count: num}); + t('Reset {{count}} entire columns', {count: num}) : + t('Reset {{count}} columns', {count: num}); + const nameDeleteColumns = t('Delete {{count}} columns', {count: num}); + const nameHideColumns = t('Hide {{count}} columns', {count: num}); const frozenMenu = options.disableFrozenMenu ? null : freezeMenuItemCmd(options); return [ frozenMenu ? [frozenMenu, menuDivider()]: null, @@ -229,7 +229,7 @@ export function freezeAction(options: IMultiColumnContextMenu): { text: string; const properNumber = numFrozen - firstColumnIndex; text = properNumber === numFrozen ? t('Unfreeze all columns') : - t('UnFreeze {{count}} columns', {count: properNumber}); + t('Unfreeze {{count}} columns', {count: properNumber}); } return { text, @@ -240,13 +240,13 @@ export function freezeAction(options: IMultiColumnContextMenu): { text: string; } } else { if (isLastFrozenSet) { - text = t('UnfreezeColumn', {count: length}); + text = t('Unfreeze {{count}} columns', {count: length}); return { text, numFrozen : numFrozen - length }; } else if (isFirstNormalSet) { - text = t('FreezeColumn', {count: length}); + text = t('Freeze {{count}} columns', {count: length}); return { text, numFrozen : numFrozen + length diff --git a/app/client/ui/SortFilterConfig.ts b/app/client/ui/SortFilterConfig.ts index 37f09796..db0626ad 100644 --- a/app/client/ui/SortFilterConfig.ts +++ b/app/client/ui/SortFilterConfig.ts @@ -51,7 +51,7 @@ export class SortFilterConfig extends Disposable { } private async _save() { - await this._docModel.docData.bundleActions(t('UpdateSortFilterSettings'), () => Promise.all([ + await this._docModel.docData.bundleActions(t('Update Sort & Filter settings'), () => Promise.all([ this._section.activeSortJson.save(), this._section.saveFilters(), ])); diff --git a/static/locales/en.client.json b/static/locales/en.client.json index dedd22f3..b97e2d4c 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -172,7 +172,7 @@ "Widget does not require any permissions.": "Widget does not require any permissions.", "Widget needs to {{read}} the current table.": "Widget needs to {{read}} the current table.", "Widget needs {{fullAccess}} to this document.": "Widget needs {{fullAccess}} to this document.", - "{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "{{wrongTypeCount}} non-{{columnType}} columns are not shown", + "{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "{{wrongTypeCount}} non-{{columnType}} column is not shown", "{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "{{wrongTypeCount}} non-{{columnType}} columns are not shown" }, "DataTables": { @@ -302,12 +302,12 @@ "Column options are limited in summary tables.": "Column options are limited in summary tables.", "Convert column to data": "Convert column to data", "Convert to trigger formula": "Convert to trigger formula", - "Data Columns_one": "Data Columns", + "Data Columns_one": "Data Column", "Data Columns_other": "Data Columns", - "Empty Columns_one": "Empty Columns", + "Empty Columns_one": "Empty Column", "Empty Columns_other": "Empty Columns", "Enter formula": "Enter formula", - "Formula Columns_one": "Formula Columns", + "Formula Columns_one": "Formula Column", "Formula Columns_other": "Formula Columns", "Make into data column": "Make into data column", "Mixed Behavior": "Mixed Behavior", @@ -337,35 +337,29 @@ "Clear values": "Clear values", "Column Options": "Column Options", "Convert formula to data": "Convert formula to data", - "DeleteColumns_one": "DeleteColumns", - "DeleteColumns_other": "DeleteColumns", + "Delete {{count}} columns_one": "Delete column", + "Delete {{count}} columns_other": "Delete {{count}} columns", "Filter Data": "Filter Data", "Freeze {{count}} columns_one": "Freeze {{count}} columns", "Freeze {{count}} columns_other": "Freeze {{count}} columns", "Freeze {{count}} more columns_one": "Freeze {{count}} more columns", "Freeze {{count}} more columns_other": "Freeze {{count}} more columns", - "FreezeColumn_one": "FreezeColumn", - "FreezeColumn_other": "FreezeColumn", - "HideColumns_one": "HideColumns", - "HideColumns_other": "HideColumns", + "Hide {{count}} columns_one": "Hide column", + "Hide {{count}} columns_other": "Hide {{count}} columns", "Insert column to the {{to}}": "Insert column to the {{to}}", "More sort options ...": "More sort options ...", "Rename column": "Rename column", - "ResetColumns_one": "ResetColumns", - "ResetColumns_other": "ResetColumns", - "ResetEntireColumns_one": "ResetEntireColumns", - "ResetEntireColumns_other": "ResetEntireColumns", + "Reset {{count}} columns_one": "Reset column", + "Reset {{count}} columns_other": "Reset {{count}} columns", + "Reset {{count}} entire columns_one": "Reset entire column", + "Reset {{count}} entire columns_other": "Reset {{count}} entire columns", "Show column {{- label}}": "Show column {{- label}}", "Sort": "Sort", "Sorted (#{{count}})_one": "Sorted (#{{count}})", "Sorted (#{{count}})_other": "Sorted (#{{count}})", - "UnFreeze {{count}} columns_one": "UnFreeze {{count}} columns", - "UnFreeze {{count}} columns_other": "UnFreeze {{count}} columns", "Unfreeze all columns": "Unfreeze all columns", "Unfreeze {{count}} columns_one": "Unfreeze {{count}} columns", - "Unfreeze {{count}} columns_other": "Unfreeze {{count}} columns", - "UnfreezeColumn_one": "UnfreezeColumn", - "UnfreezeColumn_other": "UnfreezeColumn" + "Unfreeze {{count}} columns_other": "Unfreeze {{count}} columns" }, "GristDoc": { "Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}", @@ -579,7 +573,7 @@ "Revert": "Revert", "Save": "Save", "Sort": "Sort", - "UpdateSortFilterSettings": "UpdateSortFilterSettings" + "Update Sort & Filter settings": "Update Sort & Filter settings" }, "ThemeConfig": { "Appearance ": "Appearance ", @@ -616,7 +610,7 @@ "Cancel": "Cancel", "Preview": "Preview", "Revise": "Revise", - "UpdateFormula": "UpdateFormula" + "Update formula (Shift+Enter)": "Update formula (Shift+Enter)" }, "UserManagerModel": { "Editor": "Editor", diff --git a/test/nbrowser/Localization.ts b/test/nbrowser/Localization.ts index 63df786f..b7f6fc44 100644 --- a/test/nbrowser/Localization.ts +++ b/test/nbrowser/Localization.ts @@ -112,7 +112,7 @@ describe("Localization", function() { }); it("loads correct languages from file system", async function() { - modifyByCode(tempLocale, "en", {HomeIntro: {Welcome: 'TestMessage'}}); + modifyByCode(tempLocale, "en", {HomeIntro: {'Welcome to Grist!': 'TestMessage'}}); await driver.navigate().refresh(); assert.equal(await driver.findWait('.test-welcome-title', 3000).getText(), 'TestMessage'); const gristConfig: any = await driver.executeScript("return window.gristConfig"); From 4165c35dd3e044a957ac527daa1b55425678a600 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Thu, 15 Dec 2022 10:58:12 +0100 Subject: [PATCH 13/17] Add translation keys generator script --- .gitignore | 1 + app/client/aclui/AccessRules.ts | 6 +- buildtools/generate_translation_keys.js | 75 ++++ package.json | 4 +- static/locales/en.client.json | 11 +- yarn.lock | 538 +++++++++++++++++++++++- 6 files changed, 604 insertions(+), 31 deletions(-) create mode 100644 buildtools/generate_translation_keys.js diff --git a/.gitignore b/.gitignore index af1f4bfc..d26d0f07 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /landing.db /docs/ /sandbox_venv* +/.vscode/ # Build helper files. /.build* diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index 8a558d78..5198c982 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -520,7 +520,7 @@ export class AccessRules extends Disposable { for (const tableId of tableIds) { // We don't know what the table's name was, just its tableId. // Hopefully, the user will understand. - const title = t('RemoveRulesMentioningTable', { tableId }); + const title = t('Remove {{- tableId }} rules', { tableId }); const button = bigBasicButton(title, cssRemoveIcon('Remove'), dom.on('click', async () => { await Promise.all(this._tableRules.get() .filter(rules => rules.tableId === tableId) @@ -546,7 +546,7 @@ export class AccessRules extends Disposable { }; for (const colId of colIds) { // TODO: we could translate tableId to table name in this case. - const title = t('RemoveRulesMentioningColumn', { tableId, colId }); + const title = t('Remove column {{- colId }} from {{- tableId }} rules', { tableId, colId }); const button = bigBasicButton(title, cssRemoveIcon('Remove'), dom.on('click', async () => { await Promise.all(this._tableRules.get() .filter(rules => rules.tableId === tableId) @@ -562,7 +562,7 @@ export class AccessRules extends Disposable { names: string[] ) { for (const name of names) { - const title = t('RemoveUserAttribute', {name}); + const title = t('Remove {{- name }} user attribute', {name}); const button = bigBasicButton(title, cssRemoveIcon('Remove'), dom.on('click', async () => { await Promise.all(this._userAttrRules.get() .filter(rule => rule.name.get() === name) diff --git a/buildtools/generate_translation_keys.js b/buildtools/generate_translation_keys.js new file mode 100644 index 00000000..f6bcd61f --- /dev/null +++ b/buildtools/generate_translation_keys.js @@ -0,0 +1,75 @@ +/** + * Stratégie de traduction : + * - valider sur la convention proposée par Yohan sur https://github.com/gristlabs/grist-core/issues/336 + * - migrer les anciennes clefs vers la nouvelle convention + * - soit à la main + * - soit en utilisant un script (en itérant sur le json) + * - pour les nouvelles clefs, utiliser la nouvelle convention + * - de cette manière l'anglais devrait fonctionner "tout seul", modulo éventuellement une amélioration de makeT notamment dans le cas des interpolations + * - pour les autres langues, il faudra extraire toutes les clefs dans les en.*.json + * - ci-dessous un tout début de script pour le faire sur un fichier unique + * - il faudra ensuite itérer sur tous les fichiers, et merger l'extraction avec les en.*.json existants + */ + + +const fs = require('fs'); +const path = require('path'); +const Parser = require('i18next-scanner').Parser; +const englishKeys = require('../static/locales/en.client.json'); +const _ = require('lodash'); + + +const parser = new Parser({ + keySeparator: '/', + nsSeparator: null, +}); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + // d.isDirectory() && console.log(d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +const customHandler = (fileName) => (key, options) => { + const keyWithFile = `${fileName}/${key}`; + // console.log({key, options}); + if (Object.keys(options).includes('count') === true) { + const keyOne = `${keyWithFile}_one`; + const keyOther = `${keyWithFile}_other`; + parser.set(keyOne, key); + parser.set(keyOther, key); + } else { + parser.set(keyWithFile, key); + } +}; + +const getKeysFromFile = (filePath, fileName) => { + const content = fs.readFileSync(filePath, 'utf-8'); + parser.parseFuncFromString(content, { list: [ + 'i18next.t', + 't' // To match the file-level t function created with makeT + ]}, customHandler(fileName)) + const keys = parser.get({ sort: true }); + return keys +} + +async function walkTranslation(dirPath) { + for await (const p of walk(dirPath)) { + const { name } = path.parse(p); + getKeysFromFile(p, name); + } + const keys = parser.get({sort: true}); + const newTranslations = _.merge(keys.en.translation, englishKeys); + await fs.promises.writeFile('static/locales/en.client.json', JSON.stringify(newTranslations, null, 2), 'utf-8'); + return keys; +} + +const keys = walkTranslation("app/client") +// console.log({englishKeys}); +// const keys = getKeysFromFile('app/client/ui/errorPages.ts', 'errorPages'); + + +// console.log(JSON.stringify(keys, null, 2)); \ No newline at end of file diff --git a/package.json b/package.json index d72d3ac8..f1ae84a2 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test:smoke": "NODE_PATH=_build:_build/stubs:_build/ext mocha _build/test/nbrowser/Smoke.js", "test:docker": "./test/test_under_docker.sh", "test:python": "sandbox_venv3/bin/python sandbox/grist/runtests.py ${GREP_TESTS:+discover -p \"test*${GREP_TESTS}*.py\"}", - "cli": "NODE_PATH=_build:_build/stubs:_build/ext node _build/app/server/companion.js" + "cli": "NODE_PATH=_build:_build/stubs:_build/ext node _build/app/server/companion.js", + "generate:translation": "NODE_PATH=_build:_build/stubs:_build/ext node buildtools/generate_translation_keys.js" }, "keywords": [ "grist", @@ -77,6 +78,7 @@ "chance": "1.0.16", "esbuild-loader": "2.19.0", "http-proxy": "1.18.1", + "i18next-scanner": "^4.1.0", "jsdom": "16.5.0", "mocha": "5.2.0", "mocha-webdriver": "0.2.9", diff --git a/static/locales/en.client.json b/static/locales/en.client.json index b97e2d4c..d34df205 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -26,6 +26,9 @@ "Permission to access the document in full when needed": "Permission to access the document in full when needed", "Permission to view Access Rules": "Permission to view Access Rules", "Permissions": "Permissions", + "Remove column {{- colId }} from {{- tableId }} rules": "Remove column {{- colId }} from {{- tableId }} rules", + "Remove {{- tableId }} rules": "Remove {{- tableId }} rules", + "Remove {{- name }} user attribute": "Remove {{- name }} user attribute", "Reset": "Reset", "Rules for table ": "Rules for table ", "Save": "Save", @@ -445,10 +448,6 @@ "Report a problem": "Report a problem", "Upgrade Plan": "Upgrade Plan" }, - "NTextBox": { - "false": "false", - "true": "true" - }, "OnBoardingPopups": { "Finish": "Finish", "Next": "Next" @@ -741,5 +740,9 @@ }, "sendToDrive": { "Sending file to Google Drive": "Sending file to Google Drive" + }, + "NTextBox": { + "false": "false", + "true": "true" } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 00bc9a9f..a761befc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.20.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== + dependencies: + regenerator-runtime "^0.13.11" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -820,6 +827,18 @@ accepts@~1.3.5: mime-types "~2.1.24" negotiator "0.6.2" +acorn-class-fields@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-0.3.7.tgz#a35122f3cc6ad2bb33b1857e79215677fcfdd720" + integrity sha512-jdUWSFce0fuADUljmExz4TWpPkxmRW/ZCPRqeeUzbGf0vFUcpQYbyq52l75qGd0oSwwtAepeL6hgb/naRgvcKQ== + dependencies: + acorn-private-class-elements "^0.2.7" + +acorn-dynamic-import@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" + integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" @@ -833,6 +852,11 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: version "1.8.2" resolved "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz" @@ -842,11 +866,44 @@ acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: acorn-walk "^7.0.0" xtend "^4.0.2" +acorn-private-class-elements@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz#b14902c705bcff267adede1c9f61c1a317ef95d2" + integrity sha512-+GZH2wOKNZOBI4OOPmzpo4cs6mW297sn6fgIk1dUI08jGjhAaEwvC39mN2gJAg2lmAQJ1rBkFqKWonL3Zz6PVA== + +acorn-private-methods@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/acorn-private-methods/-/acorn-private-methods-0.3.3.tgz#724414ce5b2fec733089d73a5cbba8f7beff75b1" + integrity sha512-46oeEol3YFvLSah5m9hGMlNpxDBCEkdceJgf01AjqKYTK9r6HexKs2rgSbLK81pYjZZMonhftuUReGMlbbv05w== + dependencies: + acorn-private-class-elements "^0.2.7" + +acorn-stage3@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-stage3/-/acorn-stage3-4.0.0.tgz#e8b98ae2a9991be0ba1745b5b626211086b435a8" + integrity sha512-BR+LaADtA6GTB5prkNqWmlmCLYmkyW0whvSxdHhbupTaro2qBJ95fJDEiRLPUmiACGHPaYyeH9xmNJWdGfXRQw== + dependencies: + acorn-class-fields "^0.3.7" + acorn-private-methods "^0.3.3" + acorn-static-class-features "^0.2.4" + +acorn-static-class-features@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/acorn-static-class-features/-/acorn-static-class-features-0.2.4.tgz#a0f5261dd483f25196716854f2d7652a1deb39ee" + integrity sha512-5X4mpYq5J3pdndLmIB0+WtFd/mKWnNYpuTlTzj32wUu/PMmEGOiayQ5UrqgwdBNiaZBtDDh5kddpP7Yg2QaQYA== + dependencies: + acorn-private-class-elements "^0.2.7" + acorn-walk@^7.0.0, acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^5.2.1: version "5.7.4" resolved "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz" @@ -857,6 +914,11 @@ acorn@^7.0.0, acorn@^7.1.1: resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.0.4: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + acorn@^8.0.5: version "8.8.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" @@ -981,6 +1043,13 @@ app-root-path@^3.0.0: resolved "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz" integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA== + dependencies: + buffer-equal "^1.0.0" + aproba@^1.0.3: version "1.2.0" resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" @@ -1521,6 +1590,11 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= +buffer-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90" + integrity sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg== + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" @@ -1829,7 +1903,12 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== + +clone-deep@^4.0.0, clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== @@ -1845,6 +1924,25 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag== + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" @@ -1935,6 +2033,11 @@ commander@^7.0.0: resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@^9.0.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" + integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== + components-jqueryui@1.12.1: version "1.12.1" resolved "https://registry.npmjs.org/components-jqueryui/-/components-jqueryui-1.12.1.tgz" @@ -2027,6 +2130,11 @@ content-type@~1.0.4: resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@~1.1.0: version "1.1.3" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz" @@ -2309,6 +2417,11 @@ deep-is@~0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz" @@ -2321,6 +2434,14 @@ define-properties@^1.1.2, define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + defined@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz" @@ -2484,6 +2605,16 @@ duplexer@~0.1.1: resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" @@ -2561,7 +2692,7 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2576,6 +2707,11 @@ enhanced-resolve@^5.9.3: graceful-fs "^4.2.4" tapable "^2.2.0" +ensure-type@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ensure-type/-/ensure-type-1.5.1.tgz#e65ec8d4c6ee6f35b43675d4243258cb4c8f6159" + integrity sha512-Dxe+mVF4MupV6eueWiFa6hUd9OL9lIM2/LqR40k1P+dwG+G2il2UigXTU9aQlaw+Y/N0BKSaTofNw73htTbC5g== + entities@^4.3.0: version "4.3.1" resolved "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz" @@ -2591,6 +2727,11 @@ envinfo@^7.7.3: resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== +eol@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" + integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== + error-ex@^1.2.0: version "1.3.2" resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" @@ -2822,6 +2963,11 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +esprima-next@^5.7.0: + version "5.8.4" + resolved "https://registry.yarnpkg.com/esprima-next/-/esprima-next-5.8.4.tgz#9f82c8093a33da7207a4e8621e997c66878c145a" + integrity sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg== + esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -2938,7 +3084,7 @@ express@4.16.4: utils-merge "1.0.1" vary "~1.1.2" -extend@^3.0.2, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3076,6 +3222,14 @@ flat@^4.1.0: dependencies: is-buffer "~2.0.3" +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + follow-redirects@^1.0.0: version "1.15.2" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" @@ -3172,6 +3326,14 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ== + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -3295,6 +3457,14 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -3302,6 +3472,22 @@ glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw== + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" @@ -3343,7 +3529,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.6, glob@^7.2.0: +glob@^7.1.1, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3500,6 +3686,11 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz" @@ -3510,11 +3701,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - grain-rpc@0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/grain-rpc/-/grain-rpc-0.1.7.tgz" @@ -3542,6 +3728,13 @@ gtoken@^5.0.4: google-p12-pem "^3.0.3" jws "^4.0.0" +gulp-sort@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" + integrity sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g== + dependencies: + through2 "^2.0.1" + handlebars@4.7.7: version "4.7.7" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz" @@ -3582,6 +3775,13 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" @@ -3760,6 +3960,39 @@ i18next-http-middleware@3.2.1: resolved "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.2.1.tgz" integrity sha512-zBwXxDChT0YLoTXIR6jRuqnUUhXW0Iw7egoTnNXyaDRtTbfWNXwU0a53ThyuRPQ+k+tXu3ZMNKRzfLuononaRw== +i18next-scanner@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/i18next-scanner/-/i18next-scanner-4.1.0.tgz#15979dcfd6eca494e08c8a8ff784e8740ab7d318" + integrity sha512-KAZRc6ZBqtn/Qa5fRRTn7D8effB84K9m3g9SQcKD+tocMajEVW2zfFP397qNxROad1VFjUGT8aw1Y6ZcfnIzlg== + dependencies: + acorn "^8.0.4" + acorn-dynamic-import "^4.0.0" + acorn-jsx "^5.3.1" + acorn-stage3 "^4.0.0" + acorn-walk "^8.0.0" + chalk "^4.1.0" + clone-deep "^4.0.0" + commander "^9.0.0" + deepmerge "^4.0.0" + ensure-type "^1.5.0" + eol "^0.9.1" + esprima-next "^5.7.0" + gulp-sort "^2.0.0" + i18next "*" + lodash "^4.0.0" + parse5 "^6.0.0" + sortobject "^4.0.0" + through2 "^4.0.0" + vinyl "^2.2.0" + vinyl-fs "^3.0.1" + +i18next@*: + version "22.4.5" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.5.tgz#7324e4946c2facbe743ca25bca8980af05b0a109" + integrity sha512-Kc+Ow0guRetUq+kv02tj0Yof9zveROPBAmJ8UxxNODLVBRSwsM4iD0Gw3BEieOmkWemF6clU3K1fbnCuTqiN2Q== + dependencies: + "@babel/runtime" "^7.20.6" + i18next@21.9.1, i18next@^21.0.1: version "21.9.1" resolved "https://registry.npmjs.org/i18next/-/i18next-21.9.1.tgz" @@ -3907,6 +4140,14 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -3939,7 +4180,7 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.1.0: +is-buffer@^1.1.0, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -3985,7 +4226,7 @@ is-date-object@^1.0.1: resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-extglob@^2.1.1: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= @@ -4014,6 +4255,13 @@ is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== + dependencies: + is-extglob "^2.1.0" + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" @@ -4029,6 +4277,11 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== + is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz" @@ -4108,6 +4361,13 @@ is-regex@^1.1.2: call-bind "^1.0.2" has-symbols "^1.0.1" +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + is-retry-allowed@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz" @@ -4151,11 +4411,33 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + is-url@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA== + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" @@ -4306,6 +4588,11 @@ json-schema@0.2.3: resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json-stable-stringify@~0.0.0: version "0.0.1" resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz" @@ -4465,6 +4752,13 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow== + dependencies: + flush-write-stream "^1.0.2" + levn@~0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" @@ -4633,7 +4927,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5189,6 +5483,13 @@ nopt@~1.0.10: dependencies: abbrev "1" +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -5199,6 +5500,13 @@ normalize-url@^4.1.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + npm-bundled@^1.0.1: version "1.1.1" resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz" @@ -5285,6 +5593,16 @@ object.assign@4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.0.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + object.assign@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" @@ -5316,7 +5634,7 @@ on-headers@~1.0.1, on-headers@~1.0.2: resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -5335,6 +5653,13 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw== + dependencies: + readable-stream "^2.0.1" + os-browserify@~0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz" @@ -5463,7 +5788,7 @@ parse5@*: dependencies: entities "^4.3.0" -parse5@6.0.1, parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -5483,6 +5808,11 @@ path-browserify@~0.0.0: resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" @@ -5722,16 +6052,16 @@ printj@~1.1.0: resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - process@~0.11.0: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" @@ -5782,6 +6112,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" @@ -5790,6 +6128,15 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + punycode@1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" @@ -5929,7 +6276,7 @@ read-only-stream@^2.0.0: dependencies: readable-stream "^2.0.2" -"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -5938,7 +6285,7 @@ read-only-stream@^2.0.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6051,6 +6398,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" @@ -6070,6 +6422,33 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA== + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz" @@ -6139,6 +6518,13 @@ resolve-from@^5.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A== + dependencies: + value-or-function "^3.0.0" + resolve@1.1.7: version "1.1.7" resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" @@ -6483,6 +6869,11 @@ sinon@7.1.1: supports-color "^5.5.0" type-detect "^4.0.8" +sortobject@^4.0.0: + version "4.16.0" + resolved "https://registry.yarnpkg.com/sortobject/-/sortobject-4.16.0.tgz#9f1deca8da47f0d2da0a9100d9b0a407fd9d7b3f" + integrity sha512-jdcWhqJjxyYxRcXa30qImF3PZea1GpNwdKxUac28T28+GodptH4XihPuRlgCY0hITIEQVnw8DtQ81Fb6fomBaw== + source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" @@ -6608,6 +6999,11 @@ stream-http@^2.0.0: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + stream-splicer@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz" @@ -6934,7 +7330,15 @@ thirty-two@^1.0.2: resolved "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz" integrity sha1-TKL//AKlEpDSdEueP1V2k8prYno= -through2@^2.0.0: +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -6950,6 +7354,13 @@ through2@^3.0.1: inherits "^2.0.4" readable-stream "2 || 3" +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + "through@>=2.2.7 <3", through@~2.3.4: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" @@ -6996,6 +7407,14 @@ tmp@^0.2.0, tmp@^0.2.1: dependencies: rimraf "^3.0.0" +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA== + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz" @@ -7013,6 +7432,13 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q== + dependencies: + through2 "^2.0.3" + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz" @@ -7222,6 +7648,11 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== + undeclared-identifiers@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz" @@ -7245,6 +7676,14 @@ underscore@1.12.1, underscore@>=1.8.3, underscore@^1.8.0: resolved "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" @@ -7407,6 +7846,11 @@ uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg== + vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" @@ -7421,6 +7865,54 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vinyl-fs@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA== + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^2.0.0, vinyl@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + vm-browserify@~0.0.1: version "0.0.4" resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz" @@ -7800,7 +8292,7 @@ xpath@0.0.27: resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz" integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== -xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From c18c6cb2642bac57489e08817344b391c10c1296 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Fri, 16 Dec 2022 18:10:19 +0100 Subject: [PATCH 14/17] Clean generating script and english translation values --- buildtools/generate_translation_keys.js | 69 ++++++++++++------------- package.json | 2 +- static/locales/en.client.json | 28 +++++----- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/buildtools/generate_translation_keys.js b/buildtools/generate_translation_keys.js index f6bcd61f..132be480 100644 --- a/buildtools/generate_translation_keys.js +++ b/buildtools/generate_translation_keys.js @@ -1,33 +1,26 @@ /** - * Stratégie de traduction : - * - valider sur la convention proposée par Yohan sur https://github.com/gristlabs/grist-core/issues/336 - * - migrer les anciennes clefs vers la nouvelle convention - * - soit à la main - * - soit en utilisant un script (en itérant sur le json) - * - pour les nouvelles clefs, utiliser la nouvelle convention - * - de cette manière l'anglais devrait fonctionner "tout seul", modulo éventuellement une amélioration de makeT notamment dans le cas des interpolations - * - pour les autres langues, il faudra extraire toutes les clefs dans les en.*.json - * - ci-dessous un tout début de script pour le faire sur un fichier unique - * - il faudra ensuite itérer sur tous les fichiers, et merger l'extraction avec les en.*.json existants + * Generating translations keys: + * + * This code walk through all the files in client directory and its children + * Get the all keys called by our makeT utils function + * And add only the new one on our en.client.json file + * */ - -const fs = require('fs'); -const path = require('path'); -const Parser = require('i18next-scanner').Parser; -const englishKeys = require('../static/locales/en.client.json'); -const _ = require('lodash'); - +const fs = require("fs"); +const path = require("path"); +const Parser = require("i18next-scanner").Parser; +const englishKeys = require("../static/locales/en.client.json"); +const _ = require("lodash"); const parser = new Parser({ - keySeparator: '/', + keySeparator: "/", nsSeparator: null, }); async function* walk(dir) { for await (const d of await fs.promises.opendir(dir)) { const entry = path.join(dir, d.name); - // d.isDirectory() && console.log(d.name); if (d.isDirectory()) yield* walk(entry); else if (d.isFile()) yield entry; } @@ -35,8 +28,7 @@ async function* walk(dir) { const customHandler = (fileName) => (key, options) => { const keyWithFile = `${fileName}/${key}`; - // console.log({key, options}); - if (Object.keys(options).includes('count') === true) { + if (Object.keys(options).includes("count") === true) { const keyOne = `${keyWithFile}_one`; const keyOther = `${keyWithFile}_other`; parser.set(keyOne, key); @@ -47,29 +39,34 @@ const customHandler = (fileName) => (key, options) => { }; const getKeysFromFile = (filePath, fileName) => { - const content = fs.readFileSync(filePath, 'utf-8'); - parser.parseFuncFromString(content, { list: [ - 'i18next.t', - 't' // To match the file-level t function created with makeT - ]}, customHandler(fileName)) + const content = fs.readFileSync(filePath, "utf-8"); + parser.parseFuncFromString( + content, + { + list: [ + "i18next.t", + "t", // To match the file-level t function created with makeT + ], + }, + customHandler(fileName) + ); const keys = parser.get({ sort: true }); - return keys -} + return keys; +}; async function walkTranslation(dirPath) { for await (const p of walk(dirPath)) { const { name } = path.parse(p); getKeysFromFile(p, name); } - const keys = parser.get({sort: true}); + const keys = parser.get({ sort: true }); const newTranslations = _.merge(keys.en.translation, englishKeys); - await fs.promises.writeFile('static/locales/en.client.json', JSON.stringify(newTranslations, null, 2), 'utf-8'); + await fs.promises.writeFile( + "static/locales/en.client.json", + JSON.stringify(newTranslations, null, 2), + "utf-8" + ); return keys; } -const keys = walkTranslation("app/client") -// console.log({englishKeys}); -// const keys = getKeysFromFile('app/client/ui/errorPages.ts', 'errorPages'); - - -// console.log(JSON.stringify(keys, null, 2)); \ No newline at end of file +walkTranslation("app/client"); diff --git a/package.json b/package.json index f1ae84a2..22227559 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "chance": "1.0.16", "esbuild-loader": "2.19.0", "http-proxy": "1.18.1", - "i18next-scanner": "^4.1.0", + "i18next-scanner": "4.1.0", "jsdom": "16.5.0", "mocha": "5.2.0", "mocha-webdriver": "0.2.9", diff --git a/static/locales/en.client.json b/static/locales/en.client.json index d34df205..51b7119c 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -107,11 +107,11 @@ "Clear cell": "Clear cell", "Clear values": "Clear values", "Copy anchor link": "Copy anchor link", - "Delete {{count}} columns_one": "Delete {{count}} columns", + "Delete {{count}} columns_one": "Delete column", "Delete {{count}} columns_other": "Delete {{count}} columns", - "Delete {{count}} rows_one": "Delete {{count}} rows", + "Delete {{count}} rows_one": "Delete row", "Delete {{count}} rows_other": "Delete {{count}} rows", - "Duplicate rows_one": "Duplicate rows", + "Duplicate rows_one": "Duplicate row", "Duplicate rows_other": "Duplicate rows", "Filter by this value": "Filter by this value", "Insert column to the left": "Insert column to the left", @@ -119,9 +119,9 @@ "Insert row": "Insert row", "Insert row above": "Insert row above", "Insert row below": "Insert row below", - "Reset {{count}} columns_one": "Reset {{count}} columns", + "Reset {{count}} columns_one": "Reset column", "Reset {{count}} columns_other": "Reset {{count}} columns", - "Reset {{count}} entire columns_one": "Reset {{count}} entire columns", + "Reset {{count}} entire columns_one": "Reset entire column", "Reset {{count}} entire columns_other": "Reset {{count}} entire columns" }, "ChartView": { @@ -343,9 +343,9 @@ "Delete {{count}} columns_one": "Delete column", "Delete {{count}} columns_other": "Delete {{count}} columns", "Filter Data": "Filter Data", - "Freeze {{count}} columns_one": "Freeze {{count}} columns", + "Freeze {{count}} columns_one": "Freeze this column", "Freeze {{count}} columns_other": "Freeze {{count}} columns", - "Freeze {{count}} more columns_one": "Freeze {{count}} more columns", + "Freeze {{count}} more columns_one": "Freeze one more column", "Freeze {{count}} more columns_other": "Freeze {{count}} more columns", "Hide {{count}} columns_one": "Hide column", "Hide {{count}} columns_other": "Hide {{count}} columns", @@ -361,7 +361,7 @@ "Sorted (#{{count}})_one": "Sorted (#{{count}})", "Sorted (#{{count}})_other": "Sorted (#{{count}})", "Unfreeze all columns": "Unfreeze all columns", - "Unfreeze {{count}} columns_one": "Unfreeze {{count}} columns", + "Unfreeze {{count}} columns_one": "Unfreeze this column", "Unfreeze {{count}} columns_other": "Unfreeze {{count}} columns" }, "GristDoc": { @@ -467,7 +467,7 @@ "Pages": { "Delete": "Delete", "Delete data and this page.": "Delete data and this page.", - "The following tables will no longer be visible_one": "The following tables will no longer be visible", + "The following tables will no longer be visible_one": "The following table will no longer be visible", "The following tables will no longer be visible_other": "The following tables will no longer be visible" }, "PermissionsWidget": { @@ -497,14 +497,14 @@ "COLUMN TYPE": "COLUMN TYPE", "CUSTOM": "CUSTOM", "Change Widget": "Change Widget", - "Columns_one": "Columns", + "Columns_one": "Column", "Columns_other": "Columns", "DATA TABLE": "DATA TABLE", "DATA TABLE NAME": "DATA TABLE NAME", "Data": "Data", "Detach": "Detach", "Edit Data Selection": "Edit Data Selection", - "Fields_one": "Fields", + "Fields_one": "Field", "Fields_other": "Fields", "GROUPED BY": "GROUPED BY", "ROW STYLE": "ROW STYLE", @@ -514,7 +514,7 @@ "SOURCE DATA": "SOURCE DATA", "Save": "Save", "Select Widget": "Select Widget", - "Series_one": "Series", + "Series_one": "Serie", "Series_other": "Series", "Sort & Filter": "Sort & Filter", "TRANSFORM": "TRANSFORM", @@ -526,7 +526,7 @@ "RowContextMenu": { "Copy anchor link": "Copy anchor link", "Delete": "Delete", - "Duplicate rows_one": "Duplicate rows", + "Duplicate rows_one": "Duplicate row", "Duplicate rows_other": "Duplicate rows", "Insert row": "Insert row", "Insert row above": "Insert row above", @@ -745,4 +745,4 @@ "false": "false", "true": "true" } -} \ No newline at end of file +} From 78a1c2d890603b7151834df4962ff887e302a805 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Mon, 19 Dec 2022 19:53:26 +0100 Subject: [PATCH 15/17] Add fr translation and fix some translation on the go --- app/client/aclui/AccessRules.ts | 4 +- app/client/components/RecordLayoutEditor.js | 8 +- app/client/ui/DocMenu.ts | 6 +- app/client/ui/MakeCopyMenu.ts | 2 +- app/client/ui/WelcomeQuestions.ts | 20 +- static/locales/en.client.json | 27 +- static/locales/fr.client.json | 1223 +++++++++---------- 7 files changed, 637 insertions(+), 653 deletions(-) diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index 5198c982..233bd2e0 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -1396,7 +1396,7 @@ class ObsRulePart extends Disposable { placeholder: dom.text((use) => { return ( this._ruleSet.isSoleCondition(use, this) ? t('Everyone') : - this._ruleSet.isLastCondition(use, this) ? t('EveryoneElse') : + this._ruleSet.isLastCondition(use, this) ? t('Everyone Else') : t('EnterCondition') ); }), @@ -1445,7 +1445,7 @@ class ObsRulePart extends Disposable { wide ? cssCell4.cls('') : null, this._memoEditor = aclMemoEditor(this._memo, { - placeholder: t('MemoEditorPlaceholder'), + placeholder: t("Type a message..."), }, dom.onKeyDown({ // Match the behavior of the formula editor. diff --git a/app/client/components/RecordLayoutEditor.js b/app/client/components/RecordLayoutEditor.js index e91aff4c..09116ef0 100644 --- a/app/client/components/RecordLayoutEditor.js +++ b/app/client/components/RecordLayoutEditor.js @@ -93,13 +93,13 @@ RecordLayoutEditor.prototype.buildEditorDom = function() { }; return cssControls( - basicButton(t('AddField'), cssCollapseIcon('Collapse'), + basicButton(t('Add Field'), cssCollapseIcon('Collapse'), menu((ctl) => [ - menuItem(() => addNewField(), t('CreateNewField')), + menuItem(() => addNewField(), t('Create New Field')), dom.maybe((use) => use(this._hiddenColumns).length > 0, () => menuDivider()), dom.forEach(this._hiddenColumns, (col) => - menuItem(() => showField(col), t("ShowField", {label:col.label()})) + menuItem(() => showField(col), t("Show field {{- label}}", {label:col.label()})) ), testId('edit-layout-add-menu'), ]), @@ -113,7 +113,7 @@ RecordLayoutEditor.prototype.buildEditorDom = function() { RecordLayoutEditor.prototype.buildFinishButtons = function() { return [ - primaryButton(t('SaveLayout'), + primaryButton(t('Save Layout'), dom.on('click', () => commands.allCommands.accept.run()), ), basicButton(t('Cancel'), diff --git a/app/client/ui/DocMenu.ts b/app/client/ui/DocMenu.ts index 465455cf..6d1be183 100644 --- a/app/client/ui/DocMenu.ts +++ b/app/client/ui/DocMenu.ts @@ -293,8 +293,8 @@ function buildOtherSites(home: HomeModel) { const siteName = home.app.currentOrgName; return [ dom('div', - personal ? t("You are on the {{siteName}} site. You also have access to the following sites:", {siteName}) : - t("You are on your personal site. You also have access to the following sites:"), + personal ? t("You are on your personal site. You also have access to the following sites:") : + t("You are on the {{siteName}} site. You also have access to the following sites:", {siteName}), testId('other-sites-message') ), css.otherSitesButtons( @@ -306,7 +306,7 @@ function buildOtherSites(home: HomeModel) { ) ), testId('other-sites-buttons') - ) + ), ]; }) ); diff --git a/app/client/ui/MakeCopyMenu.ts b/app/client/ui/MakeCopyMenu.ts index c1fd288a..bfdbf8b1 100644 --- a/app/client/ui/MakeCopyMenu.ts +++ b/app/client/ui/MakeCopyMenu.ts @@ -47,7 +47,7 @@ export async function replaceTrunkWithFork(user: FullUser|null, doc: Document, a 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'; + titleText = t('Original Looks Identical'); warningText = `${warningText} ${t("However, it appears to be already identical.")}`; } confirmModal(titleText, buttonText, diff --git a/app/client/ui/WelcomeQuestions.ts b/app/client/ui/WelcomeQuestions.ts index 8ddbff82..2952306f 100644 --- a/app/client/ui/WelcomeQuestions.ts +++ b/app/client/ui/WelcomeQuestions.ts @@ -65,16 +65,16 @@ export function showWelcomeQuestions(userPrefsObs: Observable): boole } const choices: Array<{icon: IconName, color: string, textKey: string}> = [ - {icon: 'UseProduct', color: `${colors.lightGreen}`, textKey: 'ProductDevelopment' }, - {icon: 'UseFinance', color: '#0075A2', textKey: 'FinanceAccounting' }, - {icon: 'UseMedia', color: '#F7B32B', textKey: 'MediaProduction' }, - {icon: 'UseMonitor', color: '#F2545B', textKey: 'ITTechnology' }, - {icon: 'UseChart', color: '#7141F9', textKey: 'Marketing' }, - {icon: 'UseScience', color: '#231942', textKey: 'Research' }, - {icon: 'UseSales', color: '#885A5A', textKey: 'Sales' }, - {icon: 'UseEducate', color: '#4A5899', textKey: 'Education' }, - {icon: 'UseHr', color: '#688047', textKey: 'HRManagement' }, - {icon: 'UseOther', color: '#929299', textKey: 'Other' }, + {icon: 'UseProduct', color: `${colors.lightGreen}`, textKey: 'Product Development' }, + {icon: 'UseFinance', color: '#0075A2', textKey: 'Finance & Accounting' }, + {icon: 'UseMedia', color: '#F7B32B', textKey: 'Media Production' }, + {icon: 'UseMonitor', color: '#F2545B', textKey: 'IT & Technology' }, + {icon: 'UseChart', color: '#7141F9', textKey: 'Marketing' }, + {icon: 'UseScience', color: '#231942', textKey: 'Research' }, + {icon: 'UseSales', color: '#885A5A', textKey: 'Sales' }, + {icon: 'UseEducate', color: '#4A5899', textKey: 'Education' }, + {icon: 'UseHr', color: '#688047', textKey: 'HR & Management' }, + {icon: 'UseOther', color: '#929299', textKey: 'Other' }, ]; function buildInfoForm(selection: Observable[], otherText: Observable) { diff --git a/static/locales/en.client.json b/static/locales/en.client.json index 51b7119c..87fed7cf 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -34,6 +34,7 @@ "Save": "Save", "Saved": "Saved", "Special Rules": "Special Rules", + "Type a message...": "Type a message...", "User Attributes": "User Attributes", "View As": "View As" }, @@ -87,6 +88,8 @@ "Create": "Create", "Remove": "Remove", "Remove API Key": "Remove API Key", + "This API key can be used to access this account anonymously via the API.": "This API key can be used to access this account anonymously via the API.", + "This API key can be used to access your account via the API. Don’t share your API key with anyone.": "This API key can be used to access your account via the API. Don’t share your API key with anyone.", "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?": "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?" }, "App": { @@ -426,6 +429,7 @@ "Organization": "Organization", "Original Has Modifications": "Original Has Modifications", "Original Looks Unrelated": "Original Looks Unrelated", + "Original Looks Identical": "Original Looks Identical", "Overwrite": "Overwrite", "Replacing the original requires editing rights on the original document.": "Replacing the original requires editing rights on the original document.", "Sign up": "Sign up", @@ -482,11 +486,11 @@ "Updating record layout.": "Updating record layout." }, "RecordLayoutEditor": { - "AddField": "AddField", - "Cancel": "Cancel", - "CreateNewField": "CreateNewField", - "SaveLayout": "SaveLayout", - "ShowField": "ShowField" + "Add Field": "Add Field", + "Create New Field": "Create New Field", + "Show field {{- label}}": "Show field {{- label}}", + "Save Layout": "Save Layout", + "Cancel": "Cancel" }, "RefSelect": { "Add Column": "Add Column", @@ -568,10 +572,10 @@ "Use choice position": "Use choice position" }, "SortFilterConfig": { - "Filter": "Filter", + "Filter": "FILTER", "Revert": "Revert", "Save": "Save", - "Sort": "Sort", + "Sort": "SORT", "Update Sort & Filter settings": "Update Sort & Filter settings" }, "ThemeConfig": { @@ -672,7 +676,16 @@ "Select All": "Select All" }, "WelcomeQuestions": { + "Education": "Education", + "Finance & Accounting": "Finance & Accounting", + "HR & Management": "HR & Management", + "IT & Technology": "IT & Technology", + "Marketing": "Marketing", + "Media Production": "Media Production", "Other": "Other", + "Product Development": "Product Development", + "Research": "Research", + "Sales": "Sales", "Type here": "Type here", "Welcome to Grist!": "Welcome to Grist!", "What brings you to Grist? Please help us serve you better.": "What brings you to Grist? Please help us serve you better." diff --git a/static/locales/fr.client.json b/static/locales/fr.client.json index a1b97cec..348928ca 100644 --- a/static/locales/fr.client.json +++ b/static/locales/fr.client.json @@ -1,772 +1,743 @@ { + "ACUserManager": { + "Invite new member": "Inviter un nouveau membre", + "Enter email address": "Entrer votre adresse e-mail", + "We'll email an invite to {{email}}": "Nous allons envoyer une invitation à {{email}}" + }, + "AccessRules": { + "Checking...": "Vérification en cours…", + "Saved": "Enregistré", + "Invalid": "Invalide", + "Save": "Enregistrer", + "Reset": "Réinitialiser", + "Add Table Rules": "Ajouter des règles pour la table", + "Add User Attributes": "Ajouter des propriétés d'utilisateur", + "Users": "Utilisateurs", + "User Attributes": "Propriétés de l'utilisateur", + "Attribute to Look Up": "Propriété d'appairage", + "Lookup Table": "Table d'appairage", + "Lookup Column": "Colonne cible", + "Default Rules": "Règles par défaut", + "Condition": "Condition", + "Permissions": "Permissions", + "Rules for table ": "Règles pour la table ", + "Add Column Rule": "Ajouter une règle de colonne", + "Add Default Rule": "Ajouter une règle par défaut", + "Delete Table Rules": "Supprimer les règles de la table", + "Special Rules": "Règles avancées", + "Allow everyone to view Access Rules.": "Autoriser tout le monde à voir les permissions avancées.", + "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Permettre à tout le monde de copier le document entier ou de le voir en mode «bac à sable».\nUtile pour faire des exemples et des modèles, mais pas pour des données sensibles.", + "Permission to view Access Rules": "Permission de voir les règles d'accès", + "Permission to access the document in full when needed": "Permission d'accéder au document dans son intégralité si nécessaire", + "Attribute name": "Nom de l’attribut", + "Everyone": "Tout le monde", + "Everyone Else": "Tous les autres", + "Type a message...": "Type a message...", + "Enter Condition": "Entrer la condition" + }, "AccountPage": { - "AccountSettings": "Paramètres du compte", + "Account settings": "Paramètres du compte", "API": "API", "Edit": "Modifier", "Email": "E-mail", "Name": "Nom", "Save": "Enregistrer", - "PasswordSecurity": "Mot de passe & Sécurité", - "LoginMethod": "Mode de connexion", - "ChangePassword": "Modifier le mot de passe", - "AllowGoogleSigning": "Autoriser la connexion à ce compte avec Google", - "TwoFactorAuth": "Authentification à deux facteurs", - "TwoFactorAuthDescription": "L'authentification à double facteur est une couche additionnelle de sécurité pour votre compte Grist qui permet de s'assurer que vous êtes la seule personne qui peut accéder à votre compte, même si quelqu'un d'autre connait votre mot de passe", + "Password & Security": "Mot de passe & Sécurité", + "Login Method": "Mode de connexion", + "Change Password": "Modifier le mot de passe", + "Allow signing in to this account with Google": "Autoriser la connexion à ce compte avec Google", + "Two-factor authentication": "Authentification à deux facteurs", + "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.": "L'authentification à double facteur est une couche additionnelle de sécurité pour votre compte Grist qui permet de s'assurer que vous êtes la seule personne qui peut accéder à votre compte, même si quelqu'un d'autre connait votre mot de passe", "Theme": "Thème", - "APIKey": "Clé d’API", - "WarningUsername": "Les noms d'utilisateurs ne doivent contenir que des lettres, des chiffres, et certains caractères spéciaux" + "API Key": "Clé d’API", + "Names only allow letters, numbers and certain special characters": "Les noms d'utilisateurs ne doivent contenir que des lettres, des chiffres, et certains caractères spéciaux" }, "AccountWidget": { - "SignIn": "Connexion", - "DocumentSettings": "Paramètres du document", - "ToggleMobileMode": "Activer/désactiver le mode mobile", + "Sign in": "Connexion", + "Document Settings": "Paramètres du document", + "Toggle Mobile Mode": "Activer/désactiver le mode mobile", "Pricing": "Tarifs", - "ProfileSettings": "Paramètres du profil", - "ManageTeam": "Gestion de l'équipe", - "AccessDetails": "Informations d’accès", - "SwitchAccounts": "Changer de compte", + "Profile Settings": "Paramètres du profil", + "Manage Team": "Gestion de l'équipe", + "Access Details": "Informations d’accès", + "Switch Accounts": "Changer de compte", "Accounts": "Comptes", - "AddAccount": "Ajouter un compte", - "SignOut": "Se déconnecter" + "Add Account": "Ajouter un compte", + "Sign Out": "Se déconnecter" + }, + "ActionLog": { + "Action Log failed to load": "Impossible de charger le journal des actions", + "Table {{tableId}} was subsequently removed in action #{{actionNum}}": "La table {{tableId}} a été ensuite supprimée dans l'action #{{actionNum}}", + "This row was subsequently removed in action {{action.actionNum}}": "Cette ligne a été ensuite supprimée dans l'action {{action.actionNum}}", + "Column {{colId}} was subsequently removed in action #{{action.actionNum}}": "La colonne {{colId}} a ensuite été supprimée dans l'action #{{action.actionNum}}" }, "AddNewButton": { - "AddNew": "Nouveau" - }, - "AppHeader": { - "HomePage": "Page d’accueil", - "Legacy": "Ancienne version", - "PersonalSite": "Espace personnel", - "TeamSite": "Espace d'équipe" + "Add New": "Nouveau" }, "ApiKey": { - "AboutToDeleteAPIKey": "Vous êtes sur le point de supprimer une clé API. Cela causera le rejet de toutes les requêtes futures utilisant cette clé API. Voulez-vous toujours la supprimer ?", - "AnonymousAPIKey": "Cette clé API peut être utilisée pour accéder à ce compte de manière anonyme via l'API.", - "ByGenerating": "En générant une clé API, vous pourrez faire des appels API pour votre propre compte.", - "ClickToShow": "Cliquer pour afficher", + "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?": "Vous êtes sur le point de supprimer une clé API. Cela causera le rejet de toutes les requêtes futures utilisant cette clé API. Voulez-vous toujours la supprimer ?", + "This API key can be used to access this account anonymously via the API.": "Cette clé API peut être utilisée pour accéder à ce compte de manière anonyme via l'API.", + "By generating an API key, you will be able to make API calls for your own account.": "En générant une clé API, vous pourrez faire des appels API pour votre propre compte.", + "Click to show": "Cliquer pour afficher", "Create": "Créer", - "OwnAPIKey": "Cette clé API peut être utilisée pour accéder à votre compte via l'API. Ne partagez pas votre clé API avec qui que ce soit.", + "This API key can be used to access your account via the API. Don’t share your API key with anyone.": "Cette clé API peut être utilisée pour accéder à votre compte via l'API. Ne partagez pas votre clé API avec qui que ce soit.", "Remove": "Supprimer", - "RemoveAPIKey": "Supprimer la clé d'API" + "Remove API Key": "Supprimer la clé d'API" }, "App": { "Description": "Description", "Key": "Clé", - "MemoryError": "Erreur mémoire" + "Memory Error": "Erreur mémoire" + }, + "AppHeader": { + "Home Page": "Page d’accueil", + "Legacy": "Ancienne version", + "Personal Site": "Espace personnel", + "Team Site": "Espace d'équipe" + }, + "AppModel": { + "This team site is suspended. Documents can be read, but not modified.": "Le site de cette équipe est suspendu. Les documents peuvent être lus, mais pas modifiés." }, "CellContextMenu": { - "ResetEntireColumns_one": "Réinitialiser la colonne entière", - "ResetEntireColumns_other": "Réinitialiser ces {{count}} colonnes entières", - "ResetColumns_one": "Réinitialiser la colonne", - "ResetColumns_other": "Réinitialiser {{count}} colonnes", - "DeleteColumns_one": "Supprimer la colonne", - "DeleteColumns_other": "Supprimer {{count}} colonnes", - "DeleteRows_one": "Supprimer la ligne", - "DeleteRows_other": "Supprimer {{count}} lignes", - "ClearValues": "Effacer les valeurs", - "ClearCell": "Effacer la cellule", - "CopyAnchorLink": "Copier l'ancre", - "FilterByValue": "Filtrer par cette valeur", - "InsertRow": "Insérer une ligne", - "InsertRowAbove": "Insérer une ligne au-dessus", - "InsertRowBelow": "Insérer une ligne au-dessous", - "DuplicateRows_one": "Dupliquer la ligne", - "DuplicateRows_other": "Dupliquer les lignes", - "InsertColumnRight": "Insérer une colonne à droite", - "InsertColumnLeft": "Insérer une colonne à gauche" + "Reset {{count}} entire columns_one": "Réinitialiser la colonne entière", + "Reset {{count}} entire columns_other": "Réinitialiser ces {{count}} colonnes entières", + "Reset {{count}} columns_one": "Réinitialiser la colonne", + "Reset {{count}} columns_other": "Réinitialiser {{count}} colonnes", + "Delete {{count}} columns_one": "Supprimer la colonne", + "Delete {{count}} columns_other": "Supprimer {{count}} colonnes", + "Delete {{count}} rows_one": "Supprimer la ligne", + "Delete {{count}} rows_other": "Supprimer {{count}} lignes", + "Clear values": "Effacer les valeurs", + "Clear cell": "Effacer la cellule", + "Copy anchor link": "Copier l'ancre", + "Filter by this value": "Filtrer par cette valeur", + "Insert row": "Insérer une ligne", + "Insert row above": "Insérer une ligne au-dessus", + "Insert row below": "Insérer une ligne au-dessous", + "Duplicate rows_one": "Dupliquer la ligne", + "Duplicate rows_other": "Dupliquer les lignes", + "Insert column to the right": "Insérer une colonne à droite", + "Insert column to the left": "Insérer une colonne à gauche" + }, + "ChartView": { + "Each Y series is followed by a series for the length of error bars.": "Each Y series is followed by a series for the length of error bars.", + "Each Y series is followed by two series, for top and bottom error bars.": "Each Y series is followed by two series, for top and bottom error bars.", + "Create separate series for each value of the selected column.": "Créer une série séparée pour chaque valeur de la colonne sélectionnée.", + "Pick a column": "Choisir une colonne", + "selected new group data columns": "selected new group data columns", + "Toggle chart aggregation": "Activer/désactiver l'agrégation des graphiques" + }, + "CodeEditorPanel": { + "Access denied": "Accès refusé", + "Code View is available only when you have full document access.": "La vue code n’est disponible que lorsque vous avez un accès complet au document." + }, + "ColorSelect": { + "Default cell style": "Style de cellule par défaut", + "Apply": "Appliquer", + "Cancel": "Annuler" }, "ColumnFilterMenu": { - "FilterByRange": "Filtrer par intervalle", + "Filter by Range": "Filtrer par intervalle", "Search": "Rechercher", - "SearchValues": "Chercher", + "Search values": "Chercher", "All": "Tous", - "AllShown": "Ces valeurs", - "AllExcept": "Pas ces valeurs", + "All Shown": "Ces valeurs", + "All Except": "Pas ces valeurs", "None": "Aucun", - "NoMatchingValues": "Aucune valeur trouvée", - "OtherMatching": "Autres correspondances", - "OtherNonMatching": "Autres non-correspondances", - "OtherValues": "Autres valeurs", - "FutureValues": "Futures valeurs", + "No matching values": "Aucune valeur trouvée", + "Other Matching": "Autres correspondances", + "Other Non-Matching": "Autres non-correspondances", + "Other Values": "Autres valeurs", + "Future Values": "Futures valeurs", "Others": "Autres" }, "CustomSectionConfig": { "Add": "Ajouter", - "EnterCustomURL": "Entrer une URL personnalisée", - "FullDocumentAccess": "Accès complet au document", - "LearnMore": "En savoir plus sur les vues personnalisées", - "PickAColumn": "Choisir une colonne", - "PickAColumnWithType": "Choisir une colonne de type {{columnType}}", - "NoDocumentAccess": "Pas d’accès au document", - "OpenConfiguration": "Ouvrir la configuration", - "Optional": " (facultatif)", - "ReadSelectedTable": "Lire les données source sélectionnées", - "SelectCustomWidget": "Sélectionner une vue personnalisée", - "WidgetNeedFullAccess": "Le widget a besoin de {{fullAccess}} à ce document.", - "WidgetNeedRead": "Le widget a besoin de {{read}} la table actuelle.", - "WidgetNoPermissison": "La vue ne nécessite aucune autorisation.", - "WrongTypesMenuText_one": "{{wrongTypeCount}} non-{{columnType}} column is not shown", - "WrongTypesMenuText_others": "{{wrongTypeCount}} non-{{columnType}} columns are not shown" + "Enter Custom URL": "Entrer une URL personnalisée", + "Full document access": "Accès complet au document", + "Learn more about custom widgets": "En savoir plus sur les vues personnalisées", + "Pick a column": "Choisir une colonne", + "Pick a {{columnType}} column": "Choisir une colonne de type {{columnType}}", + "No document access": "Pas d’accès au document", + "Open configuration": "Ouvrir la configuration", + " (optional)": " (facultatif)", + "Read selected table": "Lire les données source sélectionnées", + "Select Custom Widget": "Sélectionner une vue personnalisée", + "Widget needs {{fullAccess}} to this document.": "Le widget a besoin de {{fullAccess}} à ce document.", + "Widget needs to {{read}} the current table.": "Le widget a besoin de {{read}} la table actuelle.", + "Widget does not require any permissions.": "La vue ne nécessite aucune autorisation.", + "{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "{{wrongTypeCount}} non-{{columnType}} column is not shown", + "{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "{{wrongTypeCount}} non-{{columnType}} columns are not shown" + }, + "DataTables": { + "Raw Data Tables": "Données sources", + "Click to copy": "Cliquez ici pour copier", + "Table ID copied to clipboard": "Identifiant de table copié", + "Duplicate Table": "Dupliquer la page", + "You do not have edit access to this document": "Vous n’avez pas accès en écriture à ce document", + "Delete {{formattedTableName}} data, and remove it from all pages?": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?" }, "DocHistory": { "Activity": "Activité", "Snapshots": "Instantanés", - "OpenSnapshot": "Ouvrir cet instantané", - "CompareToCurrent": "Comparer au document en cours", - "CompareToPrevious": "Comparer au précédent", + "Open Snapshot": "Ouvrir cet instantané", + "Compare to Current": "Comparer au document en cours", + "Compare to Previous": "Comparer au précédent", "Beta": "Bêta" }, "DocMenu": { - "OtherSites": "Autres espaces", - "OtherSitesWelcome": "Tu es sur l'espace de {{siteName}}. Tu as aussi accès aux espaces suivants :", - "OtherSitesWelcome_personal": "Tu es sur ton espace personnel. Tu as aussi accès aux espaces suivants :", - "AllDocuments": "Tous les documents", - "ExamplesAndTemplates": "Exemples et modèles", - "MoreExamplesAndTemplates": "Plus d’exemples et de modèles", - "ServiceNotAvailable": "Ce service n'est pas disponible pour le moment", - "NeedPaidPlan": "(L'organisation a besoin d'un plan payant)", - "PinnedDocuments": "Documents épinglés", + "Other Sites": "Autres espaces", + "You are on the {{siteName}} site. You also have access to the following sites:": "Tu es sur l'espace de {{siteName}}. Tu as aussi accès aux espaces suivants :", + "You are on your personal site. You also have access to the following sites:": "Tu es sur ton espace personnel. Tu as aussi accès aux espaces suivants :", + "All Documents": "Tous les documents", + "Examples and Templates": "Exemples et modèles", + "More Examples and Templates": "Plus d’exemples et de modèles", + "This service is not available right now": "Ce service n'est pas disponible pour le moment", + "(The organization needs a paid plan)": "(L'organisation a besoin d'un plan payant)", + "Pinned Documents": "Documents épinglés", "Featured": "A la une", "Trash": "Corbeille", - "DocStayInTrash": "Les documents restent dans la corbeille pendant 30 jours, après quoi ils seront supprimés définitivement.", - "EmptyTrash": "La corbeille est vide.", - "WorkspaceNotFound": "Dossier introuvable", + "Documents stay in Trash for 30 days, after which they get deleted permanently.": "Les documents restent dans la corbeille pendant 30 jours, après quoi ils seront supprimés définitivement.", + "Trash is empty.": "La corbeille est vide.", + "Workspace not found": "Dossier introuvable", "Delete": "Supprimer", - "DeleteDoc": "Supprimer « {{name}}»", - "Deleted": "Supprimé {{at}}", - "Edited": "Modifié {{at}}", - "Examples&Templates": "Exemples et modèles", - "DiscoverMoreTemplates": "Découvrir plus de modèles", - "ByName": "Par nom", - "ByDateModified": "Par date de modification", - "DocumentMoveToTrash": "Le document sera déplacé vers la corbeille.", + "Delete {{name}}": "Supprimer « {{name}}»", + "Deleted {{at}}": "Supprimé {{at}}", + "Edited {{at}}": "Modifié {{at}}", + "Examples & Templates": "Exemples et modèles", + "Discover More Templates": "Découvrir plus de modèles", + "By Name": "Par nom", + "By Date Modified": "Par date de modification", + "Document will be moved to Trash.": "Le document sera déplacé vers la corbeille.", "Rename": "Renommer", "Move": "Déplacer", "Remove": "Supprimer", - "UnpinDocument": "Désépingler le document", - "PinDocument": "Épingler le document", - "AccessDetails": "Informations d’accès", - "ManageUsers": "Gérer les utilisateurs", - "DeleteForeverDoc": "Supprimer définitivement « {{name}} » ?", - "DeleteForever": "Supprimer définitivement", - "DeleteDocPerma": "Le document sera supprimé définitivement.", + "Unpin Document": "Désépingler le document", + "Pin Document": "Épingler le document", + "Access Details": "Informations d’accès", + "Manage Users": "Gérer les utilisateurs", + "Permanently Delete \"{{name}}\"?": "Supprimer définitivement « {{name}} » ?", + "Delete Forever": "Supprimer définitivement", + "Document will be permanently deleted.": "Le document sera supprimé définitivement.", "Restore": "Restaurer", - "RestoreThisDocument": "Pour restaurer ce document, il faut restaurer le dossier d'abord.", - "DeleteWorkspaceForever": "Vous pouvez supprimer définitivement un dossier une fois qu’il ne contient plus de documents.", - "CurrentWorkspace": "Dossier courant", - "RequiresEditPermissions": "Nécessite des droits d'édition", - "MoveDocToWorkspace": "Déplacer {{name}} vers le dossier" + "To restore this document, restore the workspace first.": "Pour restaurer ce document, il faut restaurer le dossier d'abord.", + "You may delete a workspace forever once it has no documents in it.": "Vous pouvez supprimer définitivement un dossier une fois qu’il ne contient plus de documents.", + "Current workspace": "Dossier courant", + "Requires edit permissions": "Nécessite des droits d'édition", + "Move {{name}} to workspace": "Déplacer {{name}} vers le dossier" + }, + "DocPageModel": { + "Error accessing document": "Erreur lors de l'accès au document", + "Reload": "Recharger", + "You can try reloading the document, or using recovery mode. Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. It also disables formulas. [{{error}}]": "Vous pouvez essayer de recharger le document ou de le passer mode récupération. Le mode de récupération ouvre le document pour être entièrement accessible aux propriétaires, et inaccessible aux autres. Il désactive également les formules. [{{error}}]", + "Sorry, access to this document has been denied. [{{error}}]": "Désolé, l’accès à ce document a été refusé. [{{error}}]", + "Document owners can attempt to recover the document. [{{error}}]": "Les propriétaires de documents peuvent tenter de récupérer le document. [{{error}}]", + "Enter recovery mode": "Passer en mode récupération", + "Add Page": "Ajouter une page", + "Add Widget to Page": "Ajouter une vue à la page", + "Add Empty Table": "Ajouter une table vide", + "You do not have edit access to this document": "Vous n’avez pas accès en écriture à ce document" }, "DocTour": { - "InvalidDocTourTitle": "No valid document tour", - "InvalidDocTourBody": "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." + "No valid document tour": "No valid document tour", + "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.": "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." }, "DocumentSettings": { - "DocumentSettings": "Paramètres du document", - "ThisDocumentID": "ID du document (pour l’API seulement) :", - "TimeZone": "Fuseau horaire :", - "Locale": "Langue :", - "Currency": "Devise :", - "LocalCurrency": "Devise locale ({{currency}})", - "EngineRisk": "Engine (experimental {{span}} change at own risk):", + "Document Settings": "Paramètres du document", + "This document's ID (for API use):": "ID du document (pour l’API seulement) :", + "Time Zone:": "Fuseau horaire :", + "Locale:": "Langue :", + "Currency:": "Devise :", + "Local currency ({{currency}})": "Devise locale ({{currency}})", + "Engine (experimental {{span}} change at own risk):": "Engine (experimental {{span}} change at own risk):", "Save": "Enregistrer", - "SaveAndReload": "Enregistrer et recharger" + "Save and Reload": "Enregistrer et recharger" + }, + "DocumentUsage": { + "Usage statistics are only available to users with full access to the document data.": "Les statistiques d'utilisation ne sont disponibles qu'aux utilisateurs ayant un accès complet aux données du document.", + "Attachments Size": "Taille des pièces jointes", + "Data Size": "Taille des données", + "Usage": "Utilisation", + "Contact the site owner to upgrade the plan to raise limits.": "Contactez l’administrateur pour mettre à niveau le plan afin de relever les limites.", + "start your 30-day free trial of the Pro plan.": "start your 30-day free trial of the Pro plan.", + "For higher limits, ": "Pour des limites plus élevées, ", + "Rows": "Lignes" + }, + "Drafts": { + "Undo discard": "Annuler la suppression", + "Restore last edit": "Restaurer la dernière modification" }, "DuplicateTable": { - "NewName": "Nom de la nouvelle table", - "AdviceWithLink": "Au lieu de dupliquer les tables, il est généralement préférable de segmenter les données en utilisant des vues liées. {{link}}", - "CopyAllData": "Copier toutes les données en plus de la structure de la table.", - "WarningACL": "Seules les règles d'accès par défaut du document s’appliqueront à la copie." - }, - "errorPages": { - "AccessDenied": "Accès refusé{{suffix}}", - "DeniedOrganizationDocuments": "Vous n’avez pas accès aux documents de cette organisation.", - "SignInWithDifferentAccount": "Vous êtes connecté en tant que {{email}}. Vous pouvez vous connecter avec un autre compte ou demander un accès à un administrateur.", - "SignInToAccess": "Connectez-vous pour accéder aux documents de cette organisation.", - "GoToMainPage": "Aller à la page principale", - "SignIn": "Connexion", - "AddAcount": "Ajouter un compte", - "SignedOut": "Déconnexion{{suffix}}", - "SignedOutNow": "Vous êtes maintenant déconnecté.", - "GenericError": "Erreur{{suffix}}", - "SignedInAgain": "Reconnectez-vous", - "PageNotFound": "Page non trouvée{{suffix}}", - "NotFoundMainText": "La page demandée n’a pas pu être trouvée.{{separator}}Veuillez vérifier l’URL et réessayer.", - "ContactSupport": "Contacter le support", - "SomethingWentWrong": "Une erreur s’est produite", - "ErrorHappened_message": "Une erreur s’est produite : {{message}}", - "ErrorHappened_unknown": "Une erreur inconnue s’est produite." - }, - "FieldConfig": { - "ColumnLabel": "LABEL ET ID DE LA COLONNE", - "ColumnOptionsLimited": "Les options des colonnes sont limitées dans les tableaux récapitulatifs.", - "ColumnType_formula_one": "Colonne formule", - "ColumnType_data_one": "Colonne de données", - "ColumnType_empty_one": "Colonne vide", - "ColumnType_formula_other": "Colonnes formule", - "ColumnType_data_other": "Colonnes de données", - "ColumnType_empty_other": "Colonnes vides", - "ColumnType_mixed_other": "Comportement mixte", - "ConvertColumn_formula": "Effacer et transformer en formule", - "ConvertColumn_data": "Convertir la colonne en données", - "ConvertColumn_triggerformula": "Convert to trigger formula", - "ClearAndReset": "Effacer et réinitialiser", - "EnterFormula": "Saisir la formule", - "ColumnBehavior": "NATURE DE COLONNE", - "SetFormula": "Définir la formule", - "SetTriggerFormula": "Définir une formule d’initialisation", - "MakeIntoDataColumn": "Transformer en colonne de données", - "TriggerFormula": "TRIGGER FORMULA" + "Name for new table": "Nom de la nouvelle table", + "Instead of duplicating tables, it's usually better to segment data using linked views. {{link}}": "Au lieu de dupliquer les tables, il est généralement préférable de segmenter les données en utilisant des vues liées. {{link}}", + "Copy all data in addition to the table structure.": "Copier toutes les données en plus de la structure de la table.", + "Only the document default access rules will apply to the copy.": "Seules les règles d'accès par défaut du document s’appliqueront à la copie." }, "ExampleInfo": { - "Title_CRM": "CRM léger", - "WelcomeTitle_CRM": "Bienvenue dans le modèle de CRM léger", - "WelcomeText_CRM": "Consultez le tutoriel associé pour savoir comment lier des données et créer des mises en page de haute productivité.", - "WelcomeTutorialName_CRM": "Tutoriel : créer un CRM", - "Title_investmentResearch": "Recherche d’investissements", - "WelcomeTitle_investmentResearch": "Bienvenue sur le modèle de recherche d'investissements", - "WelcomeText_investmentResearch": "Consulter le tutoriel associé pour apprendre à créer des tableaux récapitulatifs et des graphiques, et pour relier les graphiques de façon dynamique.", - "WelcomeTutorialName_investmentResearch": "Tutoriel : analyser et visualiser", - "Title_afterschool": "Afterschool Program", - "WelcomeTitle_afterschool": "Welcome to the Afterschool Program template", - "WelcomeText_afterschool": "Consultez e tutoriel associé pour savoir comment modéliser des données d'entreprise, utiliser les formules et gérer la complexité.", - "WelcomeTutorialName_afterschool": "Tutorial: Manage Business Data" + "Lightweight CRM": "CRM léger", + "Welcome to the Lightweight CRM template": "Bienvenue dans le modèle de CRM léger", + "Check out our related tutorial for how to link data, and create high-productivity layouts.": "Consultez le tutoriel associé pour savoir comment lier des données et créer des mises en page de haute productivité.", + "Tutorial: Create a CRM": "Tutoriel : créer un CRM", + "Investment Research": "Recherche d’investissements", + "Welcome to the Investment Research template": "Bienvenue sur le modèle de recherche d'investissements", + "Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.": "Consulter le tutoriel associé pour apprendre à créer des tableaux récapitulatifs et des graphiques, et pour relier les graphiques de façon dynamique.", + "Tutorial: Analyze & Visualize": "Tutoriel : analyser et visualiser", + "Afterschool Program": "Afterschool Program", + "Welcome to the Afterschool Program template": "Welcome to the Afterschool Program template", + "Check out our related tutorial for how to model business data, use formulas, and manage complexity.": "Consultez e tutoriel associé pour savoir comment modéliser des données d'entreprise, utiliser les formules et gérer la complexité.", + "Tutorial: Manage Business Data": "Tutorial: Manage Business Data" + }, + "FieldConfig": { + "COLUMN LABEL AND ID": "LABEL ET ID DE LA COLONNE", + "Column options are limited in summary tables.": "Les options des colonnes sont limitées dans les tableaux récapitulatifs.", + "Formula Columns_one": "Colonne formule", + "Data Columns_one": "Colonne de données", + "Empty Columns_one": "Colonne vide", + "Formula Columns_other": "Colonnes formule", + "Data Columns_other": "Colonnes de données", + "Empty Columns_other": "Colonnes vides", + "Mixed Behavior": "Comportement mixte", + "Clear and make into formula": "Effacer et transformer en formule", + "Convert column to data": "Convertir la colonne en données", + "Convert to trigger formula": "Convert to trigger formula", + "Clear and reset": "Effacer et réinitialiser", + "Enter formula": "Saisir la formule", + "COLUMN BEHAVIOR": "NATURE DE COLONNE", + "Set formula": "Définir la formule", + "Set trigger formula": "Définir une formule d’initialisation", + "Make into data column": "Transformer en colonne de données", + "TRIGGER FORMULA": "TRIGGER FORMULA" }, "FieldMenus": { - "UsingSettings_common": "Using common settings", - "UsingSettings_separate": "Using separate settings", - "Settings_useseparate": "Use separate settings", - "Settings_savecommon": "Save common settings", - "Settings_revertcommon": "Revert common settings" + "Using common settings": "Using common settings", + "Using separate settings": "Using separate settings", + "Use separate settings": "Use separate settings", + "Save as common settings": "Save common settings", + "Revert to common settings": "Revert common settings" }, "FilterConfig": { - "AddColumn": "Ajouter une colonne" + "Add Column": "Ajouter une colonne" }, "GridOptions": { - "GridOptions": "Options de la grille", - "VerticalGridlines": "Grille verticale", - "HorizontalGridlines": "Grille horizontale", - "ZebraStripes": "Couleurs alternées" + "Grid Options": "Options de la grille", + "Vertical Gridlines": "Grille verticale", + "Horizontal Gridlines": "Grille horizontale", + "Zebra Stripes": "Couleurs alternées" }, "GridViewMenus": { - "AddColumn": "Ajouter une colonne", - "ShowColumn": "Afficher la colonne {{- label}}", - "ColumnOptions": "Options de la colonne", - "FilterData": "Filtrer les données", + "Add Column": "Ajouter une colonne", + "Show column {{- label}}": "Afficher la colonne {{- label}}", + "Column Options": "Options de la colonne", + "Filter Data": "Filtrer les données", "Sort": "Trier", - "MoreSortOptions": "Plus d’options de tri…", - "RenameColumn": "Renommer la colonne", - "ResetEntireColumns_one": "Réinitialiser la colonne entière", - "ResetEntireColumns_other": "Réinitialiser {{count}} colonnes entières", - "ResetColumns_one": "Réinitialiser la colonne", - "ResetColumns_other": "Réinitialiser {{count}} colonnes", - "DeleteColumns_one": "Supprimer la colonne", - "DeleteColumns_other": "Supprimer {{count}} colonnes", - "HideColumns_one": "Masquer la colonne", - "HideColumns_other": "Masquer {{count}} colonnes", - "ConvertFormulaToData": "Convertir la formule en données", - "ClearValues": "Effacer les valeurs", - "InsertColumn": "Insérer une colonne {{to}}", - "FreezeColumn_one": "Figer cette colonne", - "FreezeColumn_other": "Figer {{count}} colonnes", - "FreezeColumn_more_one": "Figer une colonne de plus", - "FreezeColumn_more_other": "Figer {{count}} colonnes", - "UnfreezeColumn_one": "Figer cette colonne", - "UnfreezeColumn_other": "Figer {{count}} colonnes", - "UnfreezeColumn_all_other": "Libérer toutes les colonnes", - "AddToSort": "Ajouter au tri", - "AddToSort_added": "Trié (#{{count}})" + "More sort options ...": "Plus d’options de tri…", + "Rename column": "Renommer la colonne", + "Reset {{count}} entire columns_one": "Réinitialiser la colonne entière", + "Reset {{count}} entire columns_other": "Réinitialiser {{count}} colonnes entières", + "Reset {{count}} columns_one": "Réinitialiser la colonne", + "Reset {{count}} columns_other": "Réinitialiser {{count}} colonnes", + "Delete {{count}} columns_one": "Supprimer la colonne", + "Delete {{count}} columns_other": "Supprimer {{count}} colonnes", + "Hide {{count}} columns_one": "Masquer la colonne", + "Hide {{count}} columns_other": "Masquer {{count}} colonnes", + "Convert formula to data": "Convertir la formule en données", + "Clear values": "Effacer les valeurs", + "Insert column to the {{to}}": "Insérer une colonne {{to}}", + "Freeze {{count}} columns_one": "Figer cette colonne", + "Freeze {{count}} columns_other": "Figer {{count}} colonnes", + "Freeze {{count}} more columns_one": "Figer une colonne de plus", + "Freeze {{count}} more columns_other": "Figer {{count}} colonnes", + "Unfreeze {{count}} columns_one": "Figer cette colonne", + "Unfreeze {{count}} columns_other": "Figer {{count}} colonnes", + "Unfreeze all columns": "Libérer toutes les colonnes", + "Add to sort": "Ajouter au tri", + "Sorted (#{{count}})_one": "Trié (#{{count}})" + }, + "GristDoc": { + "Import from file": "Importer depuis un fichier", + "Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}", + "Saved linked section {{title}} in view {{name}}": "Saved linked section {{title}} in view {{name}}" }, "HomeIntro": { - "SignUp": "S'inscrire", - "EmptyWorkspace": "Ce dossier est vide.", - "PersonalSite": "espace personnel", - "WelcomeTo": "Bienvenue sur {{orgName}}", - "WelcomeInfoNoDocuments": "Vous avez un accès en lecture seule à ce site. Il n'y a actuellement aucun document.", - "WelcomeInfoAppearHere": "Tous les documents créés dans ce site apparaîtront ici.", - "WelcomeTextVistGrist": "Vous souhaitez utiliser Grist en dehors de votre équipe ? Visitez votre site gratuit ", - "SproutsProgram": "Sprouts Program", - "WelcomeUser": "Bienvenue sur Grist, {{name}} !", - "TeamSiteIntroGetStarted": "Pour commencer, inviter votre équipe et créer votre premier document Grist.", - "OrFindAndExpert": ", or find an expert via our ", - "PersonalIntroGetStarted": "Commencez en créant votre premier document Grist.", - "AnonIntroGetStarted": "Get started by exploring templates, or creating your first Grist document.", - "Welcome": "Bienvenue sur Grist !", - "HelpCenter": "Centre d'aide", - "InviteTeamMembers": "Inviter un nouveau membre", - "BrowseTemplates": "Parcourir les modèles", - "CreateEmptyDocument": "Créer un document vide", - "ImportDocument": "Importer un Fichier" + "Sign up": "S'inscrire", + "This workspace is empty.": "Ce dossier est vide.", + "personal site": "espace personnel", + "Welcome to {{orgName}}": "Bienvenue sur {{orgName}}", + "You have read-only access to this site. Currently there are no documents.": "Vous avez un accès en lecture seule à ce site. Il n'y a actuellement aucun document.", + "Any documents created in this site will appear here.": "Tous les documents créés dans ce site apparaîtront ici.", + "Interested in using Grist outside of your team? Visit your free ": "Vous souhaitez utiliser Grist en dehors de votre équipe ? Visitez votre site gratuit ", + "Sprouts Program": "Sprouts Program", + "Welcome to Grist, {{name}}!": "Bienvenue sur Grist, {{name}} !", + "Get started by inviting your team and creating your first Grist document.": "Pour commencer, inviter votre équipe et créer votre premier document Grist.", + "Get started by creating your first Grist document.": "Commencez en créant votre premier document Grist.", + "Get started by exploring templates, or creating your first Grist document.": "Get started by exploring templates, or creating your first Grist document.", + "Welcome to Grist!": "Bienvenue sur Grist !", + "Help Center": "Centre d'aide", + "Invite Team Members": "Inviter un nouveau membre", + "Browse Templates": "Parcourir les modèles", + "Create Empty Document": "Créer un document vide", + "Import Document": "Importer un Fichier" }, "HomeLeftPane": { - "AllDocuments": "Tous les documents", - "ExamplesAndTemplates": "Exemples & Templates", - "CreateEmptyDocument": "Créer un document vide", - "ImportDocument": "Importer un Fichier", - "CreateWorkspace": "Créer un nouveau dossier", + "All Documents": "Tous les documents", + "Examples & Templates": "Exemples & Templates", + "Create Empty Document": "Créer un document vide", + "Import Document": "Importer un Fichier", + "Create Workspace": "Créer un nouveau dossier", "Trash": "Corbeille", "Rename": "Renommer", "Delete": "Supprimer", "Workspaces": "Dossiers", - "WorkspaceDeleteTitle": "Supprimer le dossier {{workspace}} et tous les documents qu'il contient ?", - "WorkspaceDeleteText": "Le dossier va être mis à la corbeille.", - "ManageUsers": "Gérer les utilisateurs", - "AccessDetails": "Access Details" + "Delete {{workspace}} and all included documents?": "Supprimer le dossier {{workspace}} et tous les documents qu'il contient ?", + "Workspace will be moved to Trash.": "Le dossier va être mis à la corbeille.", + "Manage Users": "Gérer les utilisateurs", + "Access Details": "Access Details" + }, + "Importer": { + "Update existing records": "Update existing records", + "Merge rows that match these fields:": "Fusionner les lignes si ces champs correspondent:", + "Select fields to match on": "Sélectionner les champs pour l'appairage" }, "LeftPanelCommon": { - "HelpCenter": "Centre d'aide" + "Help Center": "Centre d'aide" }, "MakeCopyMenu": { - "CannotEditOriginal": "Replacing the original requires editing rights on the original document.", + "Replacing the original requires editing rights on the original document.": "Replacing the original requires editing rights on the original document.", "Cancel": "Annuler", - "UpdateOriginal": "Mettre à jour l'original", + "Update Original": "Mettre à jour l'original", "Update": "Mettre à jour", - "WarningOriginalWillBeUpdated": "La version originale de ce document sera mise à jour.", - "OriginalHasModifications": "L'original a été modifié", + "The original version of this document will be updated.": "La version originale de ce document sera mise à jour.", + "Original Has Modifications": "L'original a été modifié", "Overwrite": "Remplacer", - "WarningOverwriteOriginalChanges": "Attention, l'original a des modifications qui ne sont pas dans ce document. Ces modifications seront écrasées.", - "OriginalLooksUnrelated": "Original Looks Unrelated", - "WarningWillBeOverwritten": "It will be overwritten, losing any content not in this document.", - "OriginalLooksIdentical": "Original Looks Identical", - "WarningAlreadyIdentical": "However, it appears to be already identical.", - "SignUp": "Inscription", - "ToSaveSignUpAndReload": "Pour enregistrer vos modifications, veuillez vous inscrire, puis recharger cette page.", - "NoDestinationWorkspace": "Aucun dossier destination", + "Be careful, the original has changes not in this document. Those changes will be overwritten.": "Attention, l'original a des modifications qui ne sont pas dans ce document. Ces modifications seront écrasées.", + "Original Looks Unrelated": "Original Looks Unrelated", + "It will be overwritten, losing any content not in this document.": "It will be overwritten, losing any content not in this document.", + "Original Looks Identical": "Original Looks Identical", + "However, it appears to be already identical.": "However, it appears to be already identical.", + "Sign up": "Inscription", + "To save your changes, please sign up, then reload this page.": "Pour enregistrer vos modifications, veuillez vous inscrire, puis recharger cette page.", + "No destination workspace": "Aucun dossier destination", "Name": "Nom", - "EnterDocumentName": "Saisir le nom du document", - "AsTemplate": "Comme modèle", - "IncludeStructureWithoutData": "Inclure la structure sans les données.", + "Enter document name": "Saisir le nom du document", + "As Template": "Comme modèle", + "Include the structure without any of the data.": "Inclure la structure sans les données.", "Organization": "Organisation", - "NoWriteAccessToSite": "Vous n’avez pas d'accès en écriture à cet espace", + "You do not have write access to this site": "Vous n’avez pas d'accès en écriture à cet espace", "Workspace": "Dossier", - "NoWriteAccessToWorkspace": "Vous n’avez pas accès en écriture à ce dossier" + "You do not have write access to the selected workspace": "Vous n’avez pas accès en écriture à ce dossier" }, "NotifyUI": { - "UpgradePlan": "Upgrade Plan", + "Upgrade Plan": "Upgrade Plan", "Renew": "Renouveler", - "GoToPersonalSite": "Accéder à votre espace personnel", - "ErrorCannotFindPersonalSite": "Espace personnel introuvable, désolé !", - "ReportProblem": "Signaler un problème", - "AskForHelp": "Demander de l’aide", + "Go to your free personal site": "Accéder à votre espace personnel", + "Cannot find personal site, sorry!": "Espace personnel introuvable, désolé !", + "Report a problem": "Signaler un problème", + "Ask for help": "Demander de l’aide", "Notifications": "Notifications", - "GiveFeedback": "Donnez votre avis", - "NoNotifications": "Aucune notification" - }, - "NTextBox": { - "false": "faux", - "true": "vrai" + "Give feedback": "Donnez votre avis", + "No notifications": "Aucune notification" }, "OnBoardingPopups": { "Finish": "Terminer", "Next": "Suivant" }, - "WidgetTitle": { - "OverrideTitle": "Renommer la vue", - "DataTableName": "NOM DE LA TABLE", - "NewTableName": "Indiquer un nom de table", - "WidgetTitle": "TITRE DE LA VUE", - "Save": "Enregistrer", - "Cancel": "Annuler" - }, - "WelcomeQuestions": { - "WelcomeToGrist": "Bienvenue sur Grist !", - "ProductDevelopment": "Développement de produit", - "FinanceAccounting": "Finance & comptabilité", - "MediaProduction": "Production de média", - "ITTechnology": "Technologie informatique", - "Marketing": "Marketing", - "Research": "Recherche", - "Sales": "Ventes", - "Education": "Éducation", - "HRManagement": "RH & Gestion", - "Other": "Autres", - "WhatBringsYouToGrist": "Pourquoi utilisez-vous Grist ? Aidez-nous à l’améliorer.", - "TypeHere": "Écrire ici" - }, "OpenVideoTour": { - "YouTubeVideoPlayer": "Lecteur vidéo YouTube", - "GristVideoTour": "Visite guidée de Grist en vidéo", - "VideoTour": "Visite guidée en vidéo" - }, - "Pages": { - "TableWillNoLongerBeVisible_one": "La donnée source ne sera plus visible", - "TableWillNoLongerBeVisible_other": "Les données source suivantes ne seront plus visibles", - "DeleteDataAndPage": "Supprimer les données source et la page.", - "Delete": "Supprimer" + "YouTube video player": "Lecteur vidéo YouTube", + "Grist Video Tour": "Visite guidée de Grist en vidéo", + "Video Tour": "Visite guidée en vidéo" }, "PageWidgetPicker": { - "BuildingWidget": "Vue {{- label}} en construction", - "SelectWidget": "Choisir la vue", - "SelectData": "Choisir les données source", - "GroupBy": "Grouper par", - "AddToPage": "Ajouter à la page" + "Building {{- label}} widget": "Vue {{- label}} en construction", + "Select Widget": "Choisir la vue", + "Select Data": "Choisir les données source", + "Group by": "Grouper par", + "Add to Page": "Ajouter à la page" + }, + "Pages": { + "The following tables will no longer be visible_one": "La donnée source ne sera plus visible", + "The following tables will no longer be visible_other": "Les données source suivantes ne seront plus visibles", + "Delete data and this page.": "Supprimer les données source et la page.", + "Delete": "Supprimer" + }, + "PermissionsWidget": { + "Allow All": "Tout autoriser", + "Deny All": "Tout refuser", + "Read Only": "Lecture seule" + }, + "PluginScreen": { + "Import failed: ": "Échec de l'importation: " + }, + "RecordLayout": { + "Updating record layout.": "Updating record layout." + }, + "RecordLayoutEditor": { + "Add Field": "Ajouter un champ", + "Create New Field": "Créer un nouveau champ", + "Show field {{- label}}": "Afficher le champ {{- label}}", + "Save Layout": "Enregistrer cette disposition", + "Cancel": "Annuler" + }, + "RefSelect": { + "Add Column": "Ajouter une colonne", + "No columns to add": "Aucune colonne à ajouter" }, "RightPanel": { - "Column_one": "Colonne", - "Column_other": "Colonnes", - "Field_one": "Champ", - "Field_other": "Champs", + "Columns_one": "Colonne", + "Columns_other": "Colonnes", + "Fields_one": "Champ", + "Fields_other": "Champs", "Series_one": "Séries", "Series_other": "Séries", - "ColumnType": "TYPE DE COLONNE", - "Transform": "TRANSFORMER", + "COLUMN TYPE": "TYPE DE COLONNE", + "TRANSFORM": "TRANSFORMER", "Widget": "Vue", - "SortAndFilter": "Trier et Filtrer", + "Sort & Filter": "Trier et Filtrer", "Data": "Données source", - "DataTableName": "NOM DE LA TABLE", - "WidgetTitle": "TITRE DE LA VUE", - "ChangeWidget": "Modifier la vue", + "DATA TABLE NAME": "NOM DE LA TABLE", + "WIDGET TITLE": "TITRE DE LA VUE", + "Change Widget": "Modifier la vue", "Theme": "Thème", - "RowStyleUpper": "ASPECT DE LA LIGNE", - "RowStyle": "Aspect de la ligne", - "ChartType": "TYPE DE GRAPHIQUE", - "Custom": "PERSONNALISÉ", - "Sort": "TRI", - "Filter": "FILTRE", - "DataTable": "DONNÉES SOURCE", - "SourceData": "DONNÉES SOURCE", - "GroupedBy": "GROUPER PAR", - "EditDataSelection": "Données source", + "ROW STYLE": "ASPECT DE LA LIGNE", + "Row Style": "Aspect de la ligne", + "CHART TYPE": "TYPE DE GRAPHIQUE", + "CUSTOM": "PERSONNALISÉ", + "DATA TABLE": "DONNÉES SOURCE", + "SOURCE DATA": "DONNÉES SOURCE", + "GROUPED BY": "GROUPER PAR", + "Edit Data Selection": "Données source", "Detach": "Detach", - "SelectBy": "SÉLECTIONNER PAR", - "SelectWidget": "Choisir la vue", - "SelectorFor": "SÉLECTEUR", + "SELECT BY": "SÉLECTIONNER PAR", + "Select Widget": "Choisir la vue", + "SELECTOR FOR": "SÉLECTEUR", "Save": "Enregistrer", - "NoEditAccess": "Vous n’avez pas accès en écriture à ce document" + "You do not have edit access to this document": "Vous n’avez pas accès en écriture à ce document" }, "RowContextMenu": { - "InsertRow": "Insérer une ligne", - "InsertRowAbove": "Insérer une ligne au-dessus", - "InsertRowBelow": "Insérer une ligne au-dessous", - "DuplicateRows_one": "Dupliquer la ligne", - "DuplicateRows_other": "Dupliquer les lignes", + "Insert row": "Insérer une ligne", + "Insert row above": "Insérer une ligne au-dessus", + "Insert row below": "Insérer une ligne au-dessous", + "Duplicate rows_one": "Dupliquer la ligne", + "Duplicate rows_other": "Dupliquer les lignes", "Delete": "Supprimer", - "CopyAnchorLink": "Copier l'ancre" + "Copy anchor link": "Copier l'ancre" }, - "sendToDrive": { - "SendingToGoogleDrive": "Envoi en cours vers Google Drive" + "SelectionSummary": { + "Copied to clipboard": "Copié dans le presse-papier" }, "ShareMenu": { - "BackToCurrent": "Retour à la version active", - "SaveDocument": "Enregistrer le document", - "SaveCopy": "Enregistrer une copie", + "Back to Current": "Retour à la version active", + "Save Document": "Enregistrer le document", + "Save Copy": "Enregistrer une copie", "Unsaved": "Non enregistré", - "DuplicateDocument": "Dupliquer le document", - "ManageUsers": "Gérer les utilisateurs", - "AccessDetails": "Informations d’accès", - "CurrentVersion": "Version actuelle", + "Duplicate Document": "Dupliquer le document", + "Manage Users": "Gérer les utilisateurs", + "Access Details": "Informations d’accès", + "Current Version": "Version actuelle", "Original": "Original", - "ReturnToTermToUse": "Revenir à {{termToUse}}", - "ReplaceTermToUse": "Remplacer {{termToUse}}...", - "CompareTermToUse": "Comparer avec {{termToUse}}", - "WorkOnCopy": "Travailler sur une copie", - "EditWithoutAffecting": "Éditer sans affecter l'original", - "ShowInFolder": "Afficher dans le répertoire", + "Return to {{termToUse}}": "Revenir à {{termToUse}}", + "Replace {{termToUse}}...": "Remplacer {{termToUse}}...", + "Compare to {{termToUse}}": "Comparer avec {{termToUse}}", + "Work on a Copy": "Travailler sur une copie", + "Edit without affecting the original": "Éditer sans affecter l'original", + "Show in folder": "Afficher dans le répertoire", "Download": "Télécharger", - "ExportCSV": "Exporter en CSV", - "ExportXLSX": "Exporter en XLSX", - "SendToGoogleDrive": "Envoyer vers Google Drive" + "Export CSV": "Exporter en CSV", + "Export XLSX": "Exporter en XLSX", + "Send to Google Drive": "Envoyer vers Google Drive" }, "SiteSwitcher": { - "SwitchSites": "Changer d’espace", - "CreateNewTeamSite": "Créer un nouvel espace d'équipe" + "Switch Sites": "Changer d’espace", + "Create new team site": "Créer un nouvel espace d'équipe" }, - "SortConfig":{ - "AddColumn": "Ajouter une colonne", - "UpdateData": "Mettre à jour les données", - "UseChoicePosition": "Use choice position", - "NaturalSort": "Natural sort", - "EmptyValuesLast": "Valeurs vides en dernier" + "SortConfig": { + "Add Column": "Ajouter une colonne", + "Update Data": "Mettre à jour les données", + "Use choice position": "Use choice position", + "Natural sort": "Natural sort", + "Empty values last": "Valeurs vides en dernier" }, - "SortFilterConfig":{ + "SortFilterConfig": { "Save": "Enregistrer", "Revert": "Restaurer", "Sort": "TRI", "Filter": "FILTRE", - "UpdateSortFilterSettings": "Mettre à jour le tri et le filtre" + "Update Sort & Filter settings": "Mettre à jour le tri et le filtre" }, "ThemeConfig": { - "Appearance": "Apparence ", - "SyncWithOS": "Adapter l'apparence au système" + "Appearance ": "Apparence ", + "Switch appearance automatically to match system": "Adapter l'apparence au système" }, "Tools": { - "Tools": "OUTILS", - "AccessRules": "Permissions avancées", - "Data": "Données source", - "DocumentHistory": "Historique du document", - "ValidateData": "Valider les données", - "CodeView": "Vue du code", - "HowToTutorial": "Tutoriel pratique", - "DocumentTour": "Découvrir le document", - "DeleteDocumentTour": "Delete document tour?", + "TOOLS": "OUTILS", + "Access Rules": "Permissions avancées", + "Document History": "Historique du document", + "Validate Data": "Valider les données", + "Code View": "Vue du code", + "How-to Tutorial": "Tutoriel pratique", + "Tour of this Document": "Découvrir le document", + "Delete document tour?": "Delete document tour?", "Delete": "Supprimer", - "ViewingAsYourself": "Revenir à une vue en propre", - "RawData": "Données source" + "Return to viewing as yourself": "Revenir à une vue en propre", + "Raw Data": "Données source" }, "TopBar": { - "ManageTeam": "Gestion de l'équipe" + "Manage Team": "Gestion de l'équipe" }, "TriggerFormulas": { - "AnyField": "N'importe quel champ", - "NewRecords": "Nouveaux enregistrements", - "ChangesTo": "Appliquer sur les modifications à :", - "RecordChanges": "Réappliquer en cas de modification de la ligne", - "CurrentField": "Champ actif ", - "DataCleaning": "(nettoyage des données)", - "ExceptFormulas": "(sauf les formules)", + "Any field": "N'importe quel champ", + "Apply to new records": "Nouveaux enregistrements", + "Apply on changes to:": "Appliquer sur les modifications à :", + "Apply on record changes": "Réappliquer en cas de modification de la ligne", + "Current field ": "Champ actif ", "OK": "OK", "Cancel": "Annuler", "Close": "Fermer" }, - "VisibleFieldsConfig": { - "NoReorderHiddenField": "Les champs masqués ne peuvent pas être réordonnés", - "NoDropInHiddenField": "Impossible de mettre des éléments dans les champs cachés", - "SelectAll": "Sélectionner tout", - "Clear": "Effacer" + "TypeTransform": { + "Apply": "Appliquer", + "Cancel": "Annuler", + "Preview": "Aperçu", + "Revise": "Réviser", + "Update formula (Shift+Enter)": "Mettre à jour la formule (Shift+Entrée)" + }, + "UserManagerModel": { + "Owner": "Propriétaire", + "Editor": "Éditeur", + "Viewer": "Lecture seule", + "No Default Access": "Pas d’accès par défaut", + "In Full": "En entier", + "View & Edit": "Afficher et éditer", + "View Only": "Afficher seulement", + "None": "Aucun" + }, + "ValidationPanel": { + "Rule {{length}}": "Règle {{length}}", + "Update formula (Shift+Enter)": "Mettre à jour la formule (Maj+Entrée)" + }, + "ViewConfigTab": { + "Unmark On-Demand": "Unmark On-Demand", + "Make On-Demand": "Rendre dynamique", + "Advanced settings": "Paramètres avancés", + "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.": "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.", + "Form": "Formulaire", + "Compact": "Compact", + "Blocks": "Blocs", + "Edit Card Layout": "Disposition de la carte", + "Plugin: ": "Plugin: ", + "Section: ": "Section: " }, "ViewLayoutMenu": { - "DeleteRecord": "Supprimer la ligne", - "CopyAnchorLink": "Copier l'ancre", - "ShowRawData": "Afficher les données source", - "PrintWidget": "Imprimer la vue", - "DownloadCSV": "Télécharger en CSV", - "DownloadXLSX": "Télécharger en XLSX", - "EditCardLayout": "Disposition de la carte", - "WidgetOptions": "Options de la vue", - "AdvancedSortFilter": "Tri et filtre avancés", - "DataSelection": "Sélection des données", - "OpenConfiguration": "Ouvrir la configuration", - "DeleteWidget": "Supprimer la vue" + "Delete record": "Supprimer la ligne", + "Copy anchor link": "Copier l'ancre", + "Show raw data": "Afficher les données source", + "Print widget": "Imprimer la vue", + "Download as CSV": "Télécharger en CSV", + "Download as XLSX": "Télécharger en XLSX", + "Edit Card Layout": "Disposition de la carte", + "Widget options": "Options de la vue", + "Advanced Sort & Filter": "Tri et filtre avancés", + "Data selection": "Sélection des données", + "Open configuration": "Ouvrir la configuration", + "Delete widget": "Supprimer la vue" }, "ViewSectionMenu": { - "UpdateSortFilterSettings": "Mettre à jour le tri et le filtre", + "Update Sort&Filter settings": "Mettre à jour le tri et le filtre", "Save": "Enregistrer", "Revert": "Restaurer", - "SortedBy": "Trier par", - "AddFilter": "Ajouter un filtre", - "ToggleFilterBar": "Toggle Filter Bar", - "FilteredBy": "Filtré par", - "Customized": "(personnalisé)", - "Modified": "(modifié)", - "Empty": "(vide)", - "CustomOptions": "Options personnalisées", - "Sort": "TRI", - "Filter": "FILTRE" + "(customized)": "(personnalisé)", + "(modified)": "(modifié)", + "(empty)": "(vide)", + "Custom options": "Options personnalisées", + "SORT": "TRI", + "FILTER": "FILTRE" }, - "aclui": { - "AccessRules": { - "Checking": "Vérification en cours…", - "Saved": "Enregistré", - "Invalid": "Invalide", - "Save": "Enregistrer", - "Reset": "Réinitialiser", - "AddTableRules": "Ajouter des règles pour la table", - "AddUserAttributes": "Ajouter des propriétés d'utilisateur", - "Users": "Utilisateurs", - "UserAttributes": "Propriétés de l'utilisateur", - "AttributeToLookUp": "Propriété d'appairage", - "LookupTable": "Table d'appairage", - "LookupColumn": "Colonne cible", - "DefaultRules": "Règles par défaut", - "Condition": "Condition", - "Permissions": "Permissions", - "RulesForTable": "Règles pour la table ", - "AddColumnRule": "Ajouter une règle de colonne", - "AddDefaultRule": "Ajouter une règle par défaut", - "DeleteTableRules": "Supprimer les règles de la table", - "SpecialRules": "Règles avancées", - "AccessRulesDescription": "Autoriser tout le monde à voir les permissions avancées.", - "FullCopiesDescription": "Permettre à tout le monde de copier le document entier ou de le voir en mode «bac à sable».\nUtile pour faire des exemples et des modèles, mais pas pour des données sensibles.", - "AccessRulesName": "Permission de voir les règles d'accès", - "FullCopies": "Permission d'accéder au document dans son intégralité si nécessaire", - "AttributeNamePlaceholder": "Nom de l’attribut", - "Everyone": "Tout le monde", - "EveryoneElse": "Tous les autres", - "EnterCondition": "Entrer la condition" - }, - "PermissionsWidget": { - "AllowAll": "Tout autoriser", - "DenyAll": "Tout refuser", - "ReadOnly": "Lecture seule" - } + "VisibleFieldsConfig": { + "Hidden Fields cannot be reordered": "Les champs masqués ne peuvent pas être réordonnés", + "Cannot drop items into Hidden Fields": "Impossible de mettre des éléments dans les champs cachés", + "Select All": "Sélectionner tout", + "Clear": "Effacer" }, - "lib": { - "ACUserManager": { - "InviteNewMember": "Inviter un nouveau membre", - "EmailInputPlaceholder": "Entrer votre adresse e-mail", - "InviteEmail": "Nous allons envoyer une invitation à {{email}}" - } + "WelcomeQuestions": { + "Welcome to Grist!": "Bienvenue sur Grist !", + "Product Development": "Développement de produit", + "Finance & Accounting": "Finance & comptabilité", + "Media Production": "Production de média", + "IT & Technology": "Technologie informatique", + "Marketing": "Marketing", + "Research": "Recherche", + "Sales": "Ventes", + "Education": "Éducation", + "HR & Management": "RH & Gestion", + "Other": "Autres", + "What brings you to Grist? Please help us serve you better.": "Pourquoi utilisez-vous Grist ? Aidez-nous à l’améliorer.", + "Type here": "Écrire ici" }, - "models": { - "AppModel": { - "TeamSiteSuspended": "Le site de cette équipe est suspendu. Les documents peuvent être lus, mais pas modifiés." - }, - "DocPageModel": { - "ErrorAccessingDocument": "Erreur lors de l'accès au document", - "Reload": "Recharger", - "ReloadingOrRecoveryMode": "Vous pouvez essayer de recharger le document ou de le passer mode récupération. Le mode de récupération ouvre le document pour être entièrement accessible aux propriétaires, et inaccessible aux autres. Il désactive également les formules. [{{error}}]", - "AccessError_denied": "Désolé, l’accès à ce document a été refusé. [{{error}}]", - "AccessError_recover": "Les propriétaires de documents peuvent tenter de récupérer le document. [{{error}}]", - "EnterRecoveryMode": "Passer en mode récupération", - "AddPage": "Ajouter une page", - "AddWidgetToPage": "Ajouter une vue à la page", - "AddEmptyTable": "Ajouter une table vide", - "NoEditAccess": "Vous n’avez pas accès en écriture à ce document" - }, - "UserManagerModel": { - "Owner": "Propriétaire", - "Editor": "Éditeur", - "Viewer": "Lecture seule", - "NoDefaultAccess": "Pas d’accès par défaut", - "InFull": "En entier", - "ViewAndEdit": "Afficher et éditer", - "ViewOnly": "Afficher seulement", - "None": "Aucun" - } + "WidgetTitle": { + "Override widget title": "Renommer la vue", + "DATA TABLE NAME": "NOM DE LA TABLE", + "Provide a table name": "Indiquer un nom de table", + "WIDGET TITLE": "TITRE DE LA VUE", + "Save": "Enregistrer", + "Cancel": "Annuler" }, - "ui2018": { - "breadcrumbs": { - "FiddleExplanation": "Vous pouvez faire des modifications, mais une nouvelle copie sera créée et ces modifications n’affecteront pas le document original.", - "Snapshot": "instantané", - "Unsaved": "non enregistré", - "RecoveryMode": "mode récupération", - "Override": "remplacer", - "Fiddle": "bac à sable" - }, - "ColorSelect": { - "DefaultCellStyle": "Style de cellule par défaut", - "Apply": "Appliquer", - "Cancel": "Annuler" - }, - "menus": { - "SelectFields": "Sélectionner les champs", - "WorkspacesAvailableOnTeamPlans": "* Les dossiers sont disponibles avec une offre équipe. ", - "UpgradeNow": "Mettre à jour maintenant" - }, - "modals": { - "Save": "Enregistrer", - "Cancel": "Annuler", - "Ok": "Ok" - }, - "pages": { - "Rename": "Renommer", - "Remove": "Supprimer", - "DuplicatePage": "Dupliquer la page", - "NoEditAccess": "Vous n’avez pas accès en écriture à ce document" - }, - "search": { - "SearchInDocument": "Rechercher dans le document", - "NoResults": "Aucun résultat", - "FindNext": "Rechercher suivant ", - "FindPrevious": "Rechercher le précédent " - } + "breadcrumbs": { + "You may make edits, but they will create a new copy and will\nnot affect the original document.": "Vous pouvez faire des modifications, mais une nouvelle copie sera créée et ces modifications n’affecteront pas le document original.", + "snapshot": "instantané", + "unsaved": "non enregistré", + "recovery mode": "mode récupération", + "override": "remplacer", + "fiddle": "bac à sable" }, - "components": { - "ActionLog": { - "ActionLogFailed": "Impossible de charger le journal des actions", - "TableRemovedInAction": "La table {{tableId}} a été ensuite supprimée dans l'action #{{actionNum}}", - "RowRemovedInAction": "Cette ligne a été ensuite supprimée dans l'action {{action.actionNum}}", - "ColumnRemovedInAction": "La colonne {{colId}} a ensuite été supprimée dans l'action #{{action.actionNum}}" - }, - "ChartView": { - "EachYFollowedByOne": "Each Y series is followed by a series for the length of error bars.", - "EachYFollowedByTwo": "Each Y series is followed by two series, for top and bottom error bars.", - "CreateSeparateSeries": "Créer une série séparée pour chaque valeur de la colonne sélectionnée.", - "PickColumn": "Choisir une colonne", - "SelectedNewGroupDataColumns": "selected new group data columns", - "ToggleChartAggregation": "Activer/désactiver l'agrégation des graphiques" - }, - "CodeEditorPanel": { - "AccessDenied": "Accès refusé", - "CodeViewOnlyFullAccess": "La vue code n’est disponible que lorsque vous avez un accès complet au document." - }, - "DataTables": { - "RawDataTables": "Données sources", - "ClickToCopy": "Cliquez ici pour copier", - "TableIDCopied": "Identifiant de table copié", - "DuplicateTable": "Dupliquer la page", - "NoEditAccess": "Vous n’avez pas accès en écriture à ce document", - "DeleteData": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?" - }, - "DocumentUsage": { - "UsageStatisticsOnlyFullAccess": "Les statistiques d'utilisation ne sont disponibles qu'aux utilisateurs ayant un accès complet aux données du document.", - "TotalSize": "La taille totale de toutes les données de ce document, à l'exception des pièces jointes.", - "Updates": "Mise à jour toutes les 5 minutes.", - "AttachmentsSize": "Taille des pièces jointes", - "DataSize": "Taille des données", - "Usage": "Utilisation", - "LimitContactSiteOwner": "Contactez l’administrateur pour mettre à niveau le plan afin de relever les limites.", - "UpgradeLinkText": "débutez votre essai gratuit de 30 jours du forfait Pro.", - "ForHigherLimits": "Pour des limites plus élevées, ", - "StatusMessageApproachingLimit": "This document is {{- link}} free plan limits.", - "StatusMessageGracePeriod": "Limites du document {{- link}}.", - "StatusMessageGracePeriodElse": "Document limits {{- link}}. In {{gracePeriodDays}} days, this document will be read-only.", - "StatusMessageDeleteOnly": "This document {{- link}} free plan limits and is now read-only, but you can delete rows.", - "Rows": "Lignes" - }, - "ViewConfigTab": { - "UnmarkOnDemandTitle": "Unmark table On-Demand?", - "UnmarkOnDemandButton": "Unmark On-Demand", - "UnmarkOnDemandText": "If you unmark table {{- table}}' as On-Demand, its data will be loaded into the calculation engine and will be available for use in formulas. For a big table, this may greatly increase load times.{{- br}}{{-br}}Changing this setting will reload the document for all users.", - "MakeOnDemandTitle": "Rendre la table dynamique ?", - "MakeOnDemandButton": "Rendre dynamique", - "MakeOnDemandText": "If you make table {{table}} On-Demand, its data will no longer be loaded into the calculation engine and will not be available for use in formulas. It will remain available for viewing and editing.", - "AdvancedSettings": "Paramètres avancés", - "BigTablesMayBeMarked": "Big tables may be marked as \"on-demand\" to avoid loading them into the data engine.", - "Form": "Formulaire", - "Compact": "Compact", - "Blocks": "Blocs", - "EditCardLayout": "Disposition de la carte", - "PluginColon": "Plugin: ", - "SectionColon": "Section: " - }, - "Drafts": { - "UndoDiscard": "Annuler la suppression", - "RestoreLastEdit": "Restaurer la dernière modification" - }, - "duplicatePage": { - "DoesNotCopyData": "Note that this does not copy data, but creates another view of the same data.", - "DuplicatePageName": "Dupliquer la page {{pageName}}" - }, - "GristDoc": { - "ImportFromFile": "Importer depuis un fichier", - "AddedNewLinkedSection": "Added new linked section to view {{viewName}}", - "SavedLinkedSectionIn": "Saved linked section {{title}} in view {{name}}" - }, - "Importer": { - "UpdateExistingRecords": "Update existing records", - "MergeRowsThatMatch": "Fusionner les lignes si ces champs correspondent:", - "SelectFieldsToMatch": "Sélectionner les champs pour l'appairage" - }, - "PluginScreen": { - "ImportFailed": "Échec de l'importation: " - }, - "RecordLayout": { - "UpdatingRecordLayout": "Updating record layout." - }, - "RecordLayoutEditor": { - "AddField": "Ajouter un champ", - "CreateNewField": "Créer un nouveau champ", - "ShowField": "Afficher le champ {{- label}}", - "SaveLayout": "Enregistrer cette disposition", - "Cancel": "Annuler" - }, - "RefSelect": { - "AddColumn": "Ajouter une colonne", - "NoColumnsAdd": "Aucune colonne à ajouter" - }, - "SelectionSummary": { - "CopiedClipboard": "Copié dans le presse-papier" - }, - "TypeTransformation": { - "Cancel": "Annuler", - "Preview": "Aperçu", - "UpdateFormula": "Mettre à jour la formule (Shift+Entrée)", - "Revise": "Réviser", - "Apply": "Appliquer" - }, - "ValidationPanel": { - "RuleLength": "Règle {{length}}", - "UpdateFormula": "Mettre à jour la formule (Maj+Entrée)" - } + "duplicatePage": { + "Note that this does not copy data, but creates another view of the same data.": "Note that this does not copy data, but creates another view of the same data.", + "Duplicate page {{pageName}}": "Dupliquer la page {{pageName}}" + }, + "errorPages": { + "Access denied{{suffix}}": "Accès refusé{{suffix}}", + "You do not have access to this organization's documents.": "Vous n’avez pas accès aux documents de cette organisation.", + "You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.": "Vous êtes connecté en tant que {{email}}. Vous pouvez vous connecter avec un autre compte ou demander un accès à un administrateur.", + "Sign in to access this organization's documents.": "Connectez-vous pour accéder aux documents de cette organisation.", + "Go to main page": "Aller à la page principale", + "Sign in": "Connexion", + "Add account": "Ajouter un compte", + "Signed out{{suffix}}": "Déconnexion{{suffix}}", + "You are now signed out.": "Vous êtes maintenant déconnecté.", + "Error{{suffix}}": "Erreur{{suffix}}", + "Sign in again": "Reconnectez-vous", + "Page not found{{suffix}}": "Page non trouvée{{suffix}}", + "The requested page could not be found.{{separator}}Please check the URL and try again.": "La page demandée n’a pas pu être trouvée.{{separator}}Veuillez vérifier l’URL et réessayer.", + "Contact support": "Contacter le support", + "Something went wrong": "Une erreur s’est produite", + "There was an error: {{message}}": "Une erreur s’est produite : {{message}}", + "There was an unknown error.": "Une erreur inconnue s’est produite." + }, + "menus": { + "Select fields": "Sélectionner les champs", + "* Workspaces are available on team plans. ": "* Les dossiers sont disponibles avec une offre équipe. ", + "Upgrade now": "Mettre à jour maintenant" + }, + "modals": { + "Save": "Enregistrer", + "Cancel": "Annuler", + "Ok": "Ok" + }, + "pages": { + "Rename": "Renommer", + "Remove": "Supprimer", + "Duplicate Page": "Dupliquer la page", + "You do not have edit access to this document": "Vous n’avez pas accès en écriture à ce document" + }, + "search": { + "Search in document": "Rechercher dans le document", + "No results": "Aucun résultat", + "Find Next ": "Rechercher suivant ", + "Find Previous ": "Rechercher le précédent " + }, + "sendToDrive": { + "Sending file to Google Drive": "Envoi en cours vers Google Drive" + }, + "NTextBox": { + "false": "faux", + "true": "vrai" } -} +} \ No newline at end of file From 67c09d7f5a69265c2130894a0f2b28219a989882 Mon Sep 17 00:00:00 2001 From: Louis Delbosc Date: Tue, 3 Jan 2023 10:59:36 +0100 Subject: [PATCH 16/17] Fix new translations --- app/client/ui/ColumnFilterMenu.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/client/ui/ColumnFilterMenu.ts b/app/client/ui/ColumnFilterMenu.ts index d1bcf906..10986d05 100644 --- a/app/client/ui/ColumnFilterMenu.ts +++ b/app/client/ui/ColumnFilterMenu.ts @@ -141,7 +141,7 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio rangeInput( columnFilter.min, { isDateFilter, - placeholder: isDateFilter ? t('DateRangeMin') : t('RangeMin'), + placeholder: isDateFilter ? t("Start") : t("Min"), valueParser, valueFormatter, isSelected: isMinSelected, @@ -154,7 +154,7 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio rangeInput( columnFilter.max, { isDateFilter, - placeholder: isDateFilter ? t('DateRangeMax') : t('RangeMax'), + placeholder: isDateFilter ? t("End") : t("Max"), valueParser, valueFormatter, isSelected: isMaxSelected, @@ -216,7 +216,7 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio searchInput = cssSearch( searchValueObs, { onInput: true }, testId('search-input'), - { type: 'search', placeholder: t('SearchValues') }, + { type: 'search', placeholder: t('Search values') }, dom.onKeyDown({ Enter: () => { if (searchValueObs.get()) { @@ -252,14 +252,14 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio const state = use(columnFilter.state); return [ cssSelectAll( - dom.text(searchValue ? t('AllShown') : t('All')), + dom.text(searchValue ? t('All Shown') : t('All')), dom.prop('disabled', isEquivalentFilter(state, allSpec)), dom.on('click', () => columnFilter.setState(allSpec)), testId('bulk-action'), ), cssDotSeparator('•'), cssSelectAll( - searchValue ? t('AllExcept') : t('None'), + searchValue ? t('All Except') : t('None'), dom.prop('disabled', isEquivalentFilter(state, noneSpec)), dom.on('click', () => columnFilter.setState(noneSpec)), testId('bulk-action'), @@ -274,7 +274,7 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio ), cssItemList( testId('list'), - dom.maybe(use => use(filteredValues).length === 0, () => cssNoResults(t('NoMatchingValues'))), + dom.maybe(use => use(filteredValues).length === 0, () => cssNoResults(t("No matching values"))), dom.domComputed(filteredValues, (values) => values.slice(0, model.limitShown).map(([key, value]) => ( cssMenuItem( cssLabel( @@ -310,17 +310,17 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio } if (isAboveLimit) { return searchValue ? [ - buildSummary(t('OtherMatching'), valuesBeyondLimit, false, model), - buildSummary(t('OtherNonMatching'), otherValues, true, model), + buildSummary(t("Other Matching"), valuesBeyondLimit, false, model), + buildSummary(t("Other Non-Matching"), otherValues, true, model), ] : [ - buildSummary(t('OtherValues'), concat(otherValues, valuesBeyondLimit), false, model), - buildSummary(t('FutureValues'), [], true, model), + buildSummary(t("Other Values"), concat(otherValues, valuesBeyondLimit), false, model), + buildSummary(t("Future Values"), [], true, model), ]; } else { return anyOtherValues ? [ buildSummary(t('Others'), otherValues, true, model) ] : [ - buildSummary(t('FutureValues'), [], true, model) + buildSummary(t("Future Values"), [], true, model) ]; } }), From 77e85c132c73ddbb61ec651c095b2ae5e94b48a1 Mon Sep 17 00:00:00 2001 From: George Gevoian <85144792+georgegevoian@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:16:23 -0500 Subject: [PATCH 17/17] Fix control-border CSS regression (#388) --- app/client/ui2018/buttons.ts | 3 +-- app/client/ui2018/cssVars.ts | 2 +- app/common/themes/GristDark.ts | 2 +- app/common/themes/GristLight.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/client/ui2018/buttons.ts b/app/client/ui2018/buttons.ts index 83378ac2..07c78108 100644 --- a/app/client/ui2018/buttons.ts +++ b/app/client/ui2018/buttons.ts @@ -33,9 +33,8 @@ export const cssButton = styled('button', ` color: ${theme.controlFg}; --icon-color: ${theme.controlFg}; - border: ${vars.controlBorder}; + border: ${theme.controlBorder}; border-radius: ${vars.controlBorderRadius}; - border-color: ${theme.controlBorder}; cursor: pointer; diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts index 5c0c61fb..d2d88abd 100644 --- a/app/client/ui2018/cssVars.ts +++ b/app/client/ui2018/cssVars.ts @@ -359,7 +359,7 @@ export const theme = { controlDisabledBg: new CustomProp('theme-control-disabled-bg', undefined, colors.slate), controlPrimaryDisabled: new CustomProp('theme-control-primary-disabled', undefined, colors.inactiveCursor), - controlBorder: new CustomProp('theme-control-border', undefined, '#11B683'), + controlBorder: new CustomProp('theme-control-border', undefined, vars.controlBorder), /* Checkboxes */ checkboxBg: new CustomProp('theme-checkbox-bg', undefined, colors.light), diff --git a/app/common/themes/GristDark.ts b/app/common/themes/GristDark.ts index e53f2199..ada133e3 100644 --- a/app/common/themes/GristDark.ts +++ b/app/common/themes/GristDark.ts @@ -175,7 +175,7 @@ export const GristDark: ThemeColors = { 'control-disabled-fg': '#A4A4A4', 'control-disabled-bg': '#69697D', 'control-primary-disabled': '#5F8C7B', - 'control-border': '#11B683', + 'control-border': '1px solid #1DA270', /* Checkboxes */ 'checkbox-bg': '#32323F', diff --git a/app/common/themes/GristLight.ts b/app/common/themes/GristLight.ts index 2350758a..111b024d 100644 --- a/app/common/themes/GristLight.ts +++ b/app/common/themes/GristLight.ts @@ -175,7 +175,7 @@ export const GristLight: ThemeColors = { 'control-disabled-fg': '#FFFFFF', 'control-disabled-bg': '#929299', 'control-primary-disabled': '#A2E1C9', - 'control-border': '#11B683', + 'control-border': '1px solid #11B683', /* Checkboxes */ 'checkbox-bg': '#FFFFFF',