(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick
2023-01-30 09:09:11 -05:00
22 changed files with 691 additions and 118 deletions

View File

@@ -22,6 +22,8 @@ export const cssHelp = styled('div', `
export const cssRow = styled('div', `
display: flex;
flex-wrap: wrap;
row-gap: 5px;
margin: 8px 16px;
align-items: center;
color: ${theme.text};

View File

@@ -1,3 +1,4 @@
import { makeT } from 'app/client/lib/localization';
import * as commands from 'app/client/components/commands';
import { urlState } from 'app/client/models/gristUrlState';
import { IOnBoardingMsg, startOnBoarding } from "app/client/ui/OnBoardingPopups";
@@ -6,25 +7,26 @@ import { icon } from "app/client/ui2018/icons";
import { cssLink } from "app/client/ui2018/links";
import { dom, styled } from "grainjs";
const t = makeT('WelcomeTour');
export const welcomeTour: IOnBoardingMsg[] = [
{
title: 'Editing Data',
title: t('Editing Data'),
body: () => [
dom('p',
'Double-click or hit ', Key(KeyContent('Enter')), ' on a cell to edit it. ',
'Start with ', Key(KeyStrong('=')), ' to enter a formula.'
)
t('Double-click or hit {{enter}} on a cell to edit it. ', {enter: Key(KeyContent(t('Enter')))}),
t('Start with {{equal}} to enter a formula.', { equal: Key(KeyStrong('=')) }))
],
selector: '.field_clip',
placement: 'bottom',
},
{
selector: '.tour-creator-panel',
title: 'Configuring your document',
title: t('Configuring your document'),
body: () => [
dom('p',
'Toggle the ', dom('em', 'creator panel'), ' to format columns, ',
'convert to card view, select data, and more.'
t('Toggle the {{creatorPanel}} to format columns, ', {creatorPanel: dom('em', t('creator panel'))}),
t('convert to card view, select data, and more.')
)
],
placement: 'left',
@@ -32,50 +34,53 @@ export const welcomeTour: IOnBoardingMsg[] = [
},
{
selector: '.tour-type-selector',
title: 'Customizing columns',
title: t('Customizing columns'),
body: () => [
dom('p',
'Set formatting options, formulas, or column types, such as dates, choices, or attachments. '),
t('Set formatting options, formulas, or column types, such as dates, choices, or attachments. ')),
dom('p',
'Make it relational! Use the ', Key('Reference'), ' type to link tables. '
t('Make it relational! Use the {{ref}} type to link tables. ', {ref: Key(t('Reference'))}),
)
],
placement: 'right',
},
{
selector: '.tour-add-new',
title: 'Building up',
title: t('Building up'),
body: () => [
dom('p', 'Use ', Key('Add New'), ' to add widgets, pages, or import more data. ')
dom('p', t('Use {{addNew}} to add widgets, pages, or import more data. ', {addNew: Key(t('Add New'))}))
],
placement: 'right',
},
{
selector: '.tour-share-icon',
title: 'Sharing',
title: t('Sharing'),
body: () => [
dom('p', 'Use the Share button (', TopBarButtonIcon('Share'), ') to share the document or export data.')
dom('p', t('Use the Share button ({{share}}) to share the document or export data.',
{share: TopBarButtonIcon(t('Share'))}))
],
placement: 'bottom',
cropPadding: true,
},
{
selector: '.tour-help-center',
title: 'Flying higher',
title: t('Flying higher'),
body: () => [
dom('p', 'Use ', Key(GreyIcon('Help'), 'Help Center'), ' for documentation or questions.'),
dom('p', t('Use {{helpCenter}} for documentation or questions.',
{helpCenter: Key(GreyIcon('Help'), t('Help Center'))})),
],
placement: 'right',
},
{
selector: '.tour-welcome',
title: 'Welcome to Grist!',
title: t('Welcome to Grist!'),
body: () => [
dom('p', 'Browse our ',
cssLink({target: '_blank', href: urlState().makeUrl({homePage: "templates"})},
'template library', cssInlineIcon('FieldLink')),
"to discover what's possible and get inspired."
),
dom('p', t("Browse our {{templateLibrary}} to discover what's possible and get inspired.",
{
templateLibrary: cssLink({ target: '_blank', href: urlState().makeUrl({ homePage: "templates" }) },
t('template library'), cssInlineIcon('FieldLink'))
}
)),
],
showHasModal: true,
}

View File

@@ -1,3 +1,4 @@
import { makeT } from 'app/client/lib/localization';
import {allCommands} from 'app/client/components/commands';
import {GristDoc} from 'app/client/components/GristDoc';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
@@ -7,6 +8,8 @@ import {theme, vars} from 'app/client/ui2018/cssVars';
import {ConditionalStyle} from 'app/client/widgets/ConditionalStyle';
import {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs';
const t = makeT('CellStyle');
export class CellStyle extends Disposable {
constructor(
@@ -20,8 +23,8 @@ export class CellStyle extends Disposable {
public buildDom(): DomContents {
return [
cssLine(
cssLabel('CELL STYLE'),
cssButton('Open row styles', dom.on('click', allCommands.viewTabOpen.run)),
cssLabel(t('CELL STYLE')),
cssButton(t('Open row styles'), dom.on('click', allCommands.viewTabOpen.run)),
),
cssRow(
dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => {
@@ -58,12 +61,12 @@ export class CellStyle extends Disposable {
}, {
onSave: () => options.save(),
onRevert: () => options.revert(),
placeholder: use => use(hasMixedStyle) ? 'Mixed style' : 'Default cell style'
placeholder: use => use(hasMixedStyle) ? t('Mixed style') : t('Default cell style')
}
);
}),
),
dom.create(ConditionalStyle, "Cell Style", this._field, this._gristDoc, fromKo(this._field.config.multiselect))
dom.create(ConditionalStyle, t("Cell Style"), this._field, this._gristDoc, fromKo(this._field.config.multiselect))
];
}
}

View File

@@ -6,20 +6,20 @@
height: 16px;
}
.field_clip.has_cursor > .widget_checkbox {
:not(.formula_field)>.field_clip.has_cursor>.widget_checkbox {
cursor: pointer;
box-shadow: inset 0 0 0 1px #606060;
border-radius: 3px;
background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(252,252,252,1) 29%, rgba(239,239,239,1) 50%, rgba(232,232,232,1) 50%, rgba(242,242,242,1) 100%);
}
.widget_checkbox:hover:not(.disabled) {
:not(.formula_field)>.field_clip>.widget_checkbox:hover:not(.disabled) {
cursor: pointer;
box-shadow: inset 0 0 0 1px #606060;
border-radius: 3px;
background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(252,252,252,1) 29%, rgba(239,239,239,1) 50%, rgba(232,232,232,1) 50%, rgba(242,242,242,1) 100%);
}
.widget_checkbox:active:not(.disabled) {
:not(.formula_field)>.field_clip>.widget_checkbox:active:not(.disabled) {
background: linear-gradient(to bottom, rgba(147,180,242,1) 0%, rgba(135,168,233,1) 10%, rgba(115,149,218,1) 25%, rgba(115,150,224,1) 37%, rgba(115,153,230,1) 50%, rgba(86,134,219,1) 51%, rgba(130,174,235,1) 83%, rgba(151,194,243,1) 100%);
}

View File

@@ -1,3 +1,4 @@
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {KoSaveableObservable} from 'app/client/models/modelUtil';
@@ -13,6 +14,8 @@ export type IChoiceOptions = Style
export type ChoiceOptions = Record<string, IChoiceOptions | undefined>;
export type ChoiceOptionsByName = Map<string, IChoiceOptions | undefined>;
const t = makeT('ChoiceTextBox');
export function getRenderFillColor(choiceOptions?: IChoiceOptions) {
return choiceOptions?.fillColor ?? DEFAULT_FILL_COLOR;
}
@@ -78,7 +81,7 @@ export class ChoiceTextBox extends NTextBox {
);
return [
super.buildConfigDom(),
cssLabel('CHOICES'),
cssLabel(t('CHOICES')),
cssRow(
dom.autoDispose(disabled),
dom.autoDispose(mixed),

View File

@@ -1,3 +1,4 @@
import {makeT} from 'app/client/lib/localization';
import {GristDoc} from 'app/client/components/GristDoc';
import {ColumnRec} from 'app/client/models/DocModel';
import {KoSaveableObservable} from 'app/client/models/modelUtil';
@@ -18,6 +19,7 @@ import {Computed, Disposable, dom, DomContents, makeTestId, Observable, styled}
import debounce = require('lodash/debounce');
const testId = makeTestId('test-widget-style-');
const t = makeT('ConditionalStyle');
export class ConditionalStyle extends Disposable {
// Holds data from currently selected record (holds data only when this field has conditional styles).
@@ -71,12 +73,12 @@ export class ConditionalStyle extends Disposable {
{ style: 'margin-top: 16px' },
withInfoTooltip(
textButton(
'Add conditional style',
t('Add conditional style'),
testId('add-conditional-style'),
dom.on('click', () => this._ruleOwner.addEmptyRule()),
dom.prop('disabled', this._disabled),
),
(this._label === 'Row Style'
(this._label === t('Row Style')
? GristTooltips.addRowConditionalStyle()
: GristTooltips.addColumnConditionalStyle()
),
@@ -113,8 +115,8 @@ export class ConditionalStyle extends Disposable {
const errorMessage = Computed.create(owner, use => {
const value = use(currentValue);
return (!use(hasError) ? '' :
isRaisedException(value) ? 'Error in style rule' :
'Rule must return True or False');
isRaisedException(value) ? t('Error in style rule') :
t('Rule must return True or False'));
});
return dom('div',
testId(`conditional-rule-${ruleIndex}`),
@@ -153,7 +155,7 @@ export class ConditionalStyle extends Disposable {
)
),
cssRow(
textButton('Add another rule',
textButton(t('Add another rule'),
dom.on('click', () => this._ruleOwner.addEmptyRule()),
testId('add-another-rule'),
dom.prop('disabled', use => this._disabled && use(this._disabled))

View File

@@ -1,9 +1,12 @@
import { makeT } from 'app/client/lib/localization';
import {ACSelectItem, buildACSelect} from "app/client/lib/ACSelect";
import {Computed, IDisposableOwner, Observable} from "grainjs";
import {ACIndexImpl} from "app/client/lib/ACIndex";
import {testId} from 'app/client/ui2018/cssVars';
import {currencies} from 'app/common/Locales';
const t = makeT('CurrencyPicker');
interface CurrencyPickerOptions {
// The label to use in the select menu for the default option.
defaultCurrencyLabel: string;
@@ -40,7 +43,7 @@ export function buildCurrencyPicker(
save(_, item: ACSelectItem | undefined) {
// Save only if we have found a match
if (!item) {
throw new Error("Invalid currency");
throw new Error(t("Invalid currency"));
}
// For default value, return undefined to use default currency for document.
onSave(item.value === defaultCurrencyLabel ? undefined : item.value);

View File

@@ -1,4 +1,5 @@
import {GristDoc} from 'app/client/components/GristDoc';
import {makeT} from 'app/client/lib/localization';
import {FocusLayer} from 'app/client/lib/FocusLayer';
import {createObsArray} from 'app/client/lib/koArrayWrap';
import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
@@ -35,6 +36,7 @@ import maxSize from 'popper-max-size-modifier';
import flatMap = require('lodash/flatMap');
const testId = makeTestId('test-discussion-');
const t = makeT('DiscussionEditor');
const COMMENTS_LIMIT = 200;
interface DiscussionPopupProps {
@@ -68,7 +70,7 @@ export class CellWithComments extends Disposable implements ICellView {
public async reply(comment: CellRec, text: string): Promise<void> {
const author = commentAuthor(this.gristDoc);
await this.gristDoc.docData.bundleActions("Reply to a comment", () => Promise.all([
await this.gristDoc.docData.bundleActions(t("Reply to a comment"), () => Promise.all([
this.gristDoc.docModel.cells.sendTableAction([
"AddRecord",
null,
@@ -150,7 +152,7 @@ export class EmptyCell extends CellWithComments implements ICellView {
})
}
];
await props.gristDoc.docData.sendActions([addComment], 'Started discussion');
await props.gristDoc.docData.sendActions([addComment], t('Started discussion'));
}
}
@@ -221,9 +223,9 @@ class EmptyCellView extends Disposable {
text: this._newText,
onSave: () => this.props.onSave(this._newText.get()),
onCancel: () => this.props.closeClicked?.(),
editorArgs: [{placeholder: 'Write a comment'}],
mainButton: 'Comment',
buttons: ['Cancel'],
editorArgs: [{placeholder: t('Write a comment')}],
mainButton: t('Comment'),
buttons: [t('Cancel')],
args: [testId('editor-start')]
}));
}
@@ -274,7 +276,7 @@ class CellWithCommentsView extends Disposable implements IDomComponent {
public buildDom() {
return cssTopic(
dom.maybe(this._truncated, () => cssTruncate(`Showing last ${COMMENTS_LIMIT} comments`)),
dom.maybe(this._truncated, () => cssTruncate(t("Showing last {{nb}} comments", {nb: COMMENTS_LIMIT}))),
cssTopic.cls('-panel', this.props.panel),
domOnCustom(CommentView.EDIT, (s: CommentView) => this._onEditComment(s)),
domOnCustom(CommentView.CANCEL, (s: CommentView) => this._onCancelEdit()),
@@ -358,7 +360,7 @@ class CellWithCommentsView extends Disposable implements IDomComponent {
onSave: () => this._save(),
onCancel: () => this.props.closeClicked?.(),
mainButton: 'Send',
editorArgs: [{placeholder: 'Comment'}],
editorArgs: [{placeholder: t('Comment')}],
args: [testId('editor-add')]
}));
}
@@ -464,8 +466,8 @@ class CommentView extends Disposable {
const text = Observable.create(owner, comment.text.peek() ?? '');
return dom.create(CommentEntry, {
text,
mainButton: 'Save',
buttons: ['Cancel'],
mainButton: t('Save'),
buttons: [t('Cancel')],
onSave: async () => {
const value = text.get();
text.set("");
@@ -503,7 +505,7 @@ class CommentView extends Disposable {
dom.maybe(use => !use(this.isEditing) && !this.props.isReply && !use(comment.resolved),
() => dom.domComputed(use => {
if (!use(this.replying)) {
return cssReplyButton(icon('Message'), 'Reply',
return cssReplyButton(icon('Message'), t('Reply'),
testId('comment-reply-button'),
dom.on('click', withStop(() => this.replying.set(true))),
dom.style('margin-left', use2 => use2(this._hasReplies) ? '16px' : '0px'),
@@ -513,8 +515,8 @@ class CommentView extends Disposable {
return dom.create(CommentEntry, {
text,
args: [dom.style('margin-top', '8px'), testId('editor-reply')],
mainButton: 'Reply',
buttons: ['Cancel'],
mainButton: t('Reply'),
buttons: [t('Cancel')],
onSave: async () => {
const value = text.get();
this.replying.set(false);
@@ -522,7 +524,7 @@ class CommentView extends Disposable {
},
onCancel: () => this.replying.set(false),
onClick: (button) => {
if (button === 'Cancel') {
if (button === t('Cancel')) {
this.replying.set(false);
}
},
@@ -538,7 +540,7 @@ class CommentView extends Disposable {
testId('comment-resolved'),
icon('FieldChoice'),
cssResolvedText(dom.text(
`Marked as resolved`
t(`Marked as resolved`)
)));
}),
]),
@@ -554,23 +556,23 @@ class CommentView extends Disposable {
!canResolve ? null :
menuItem(
() => this.props.topic.resolve(this.props.comment),
'Resolve'
t('Resolve')
),
!comment.resolved() ? null :
menuItem(
() => this.props.topic.open(comment),
'Open'
t('Open')
),
menuItem(
() => this.props.topic.remove(comment),
'Remove',
t('Remove'),
dom.cls('disabled', use => {
return currentUser !== use(comment.userRef);
})
),
menuItem(
() => this._edit(),
'Edit',
t('Edit'),
dom.cls('disabled', use => {
return currentUser !== use(comment.userRef);
})
@@ -605,7 +607,7 @@ class CommentEntry extends Disposable {
public buildDom() {
const text = this.props.text;
const clickBuilder = (button: string) => dom.on('click', () => {
if (button === "Cancel") {
if (button === t("Cancel")) {
this.props.onCancel?.();
} else {
this.props.onClick?.(button);
@@ -699,9 +701,9 @@ export class DiscussionPanel extends Disposable implements IDomComponent {
const tables = Computed.create(owner, use => {
// Filter out those tables that are not available by ACL.
if (use(this._currentPageKo)) {
return [...new Set(use(viewSections).map(vs => use(vs.table)).filter(t => use(t.tableId)))];
return [...new Set(use(viewSections).map(vs => use(vs.table)).filter(tb => use(tb.tableId)))];
} else {
return use(this._grist.docModel.visibleTables.getObservable()).filter(t => use(t.tableId));
return use(this._grist.docModel.visibleTables.getObservable()).filter(tb => use(tb.tableId));
}
});
@@ -774,8 +776,8 @@ export class DiscussionPanel extends Disposable implements IDomComponent {
;
});
const allDiscussions = Computed.create(owner, use => {
const list = flatMap(flatMap(use(tables).map(t => {
const columns = use(use(t.columns).getObservable());
const list = flatMap(flatMap(use(tables).map(tb => {
const columns = use(use(tb.columns).getObservable());
const dList = columns.map(col => use(use(col.cells).getObservable())
.filter(c => use(c.root) && use(c.type) === CellInfoType.COMMENT));
return dList;
@@ -818,9 +820,9 @@ export class DiscussionPanel extends Disposable implements IDomComponent {
testId('panel-menu'),
menu(() => {
return [cssDropdownMenu(
labeledSquareCheckbox(this._onlyMine, "Only my threads", testId('my-threads')),
labeledSquareCheckbox(this._currentPage, "Only current page", testId('only-page')),
labeledSquareCheckbox(this._resolved, "Show resolved comments", testId('show-resolved')),
labeledSquareCheckbox(this._onlyMine, t("Only my threads"), testId('my-threads')),
labeledSquareCheckbox(this._currentPage, t("Only current page"), testId('only-page')),
labeledSquareCheckbox(this._resolved, t("Show resolved comments"), testId('show-resolved')),
)];
}, {placement: 'bottom-start'}),
dom.on('click', stopPropagation)

View File

@@ -1,13 +1,16 @@
import {makeT} from 'app/client/lib/localization';
import {ITooltipControl, showTooltip, tooltipCloseButton} from 'app/client/ui/tooltips';
import {colors, testId} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links';
import {dom, styled} from 'grainjs';
const t = makeT('EditorTooltip');
export function showTooltipToCreateFormula(editorDom: HTMLElement, convert: () => void) {
function buildTooltip(ctl: ITooltipControl) {
return cssConvertTooltip(icon('Convert'),
cssLink('Convert column to formula',
cssLink(t('Convert column to formula'),
dom.on('mousedown', (ev) => { ev.preventDefault(); convert(); }),
testId('editor-tooltip-convert'),
),

View File

@@ -9,6 +9,7 @@ import { KoArray } from 'app/client/lib/koArray';
import * as kd from 'app/client/lib/koDom';
import * as kf from 'app/client/lib/koForm';
import * as koUtil from 'app/client/lib/koUtil';
import { makeT } from 'app/client/lib/localization';
import { reportError } from 'app/client/models/AppModel';
import { DataRowModel } from 'app/client/models/DataRowModel';
import { ColumnRec, DocModel, ViewFieldRec } from 'app/client/models/DocModel';
@@ -38,7 +39,7 @@ import * as ko from 'knockout';
import * as _ from 'underscore';
const testId = makeTestId('test-fbuilder-');
const t = makeT('FieldBuilder');
// Creates a FieldBuilder object for each field in viewFields
@@ -228,7 +229,7 @@ export class FieldBuilder extends Disposable {
defaultWidget.onWrite((value) => this.field.config.widget(value));
const disabled = Computed.create(null, use => !use(this.field.config.sameWidgets));
return [
cssLabel('CELL FORMAT'),
cssLabel(t('CELL FORMAT')),
cssRow(
grainjsDom.autoDispose(defaultWidget),
widgetOptions.length <= 2 ?
@@ -242,7 +243,7 @@ export class FieldBuilder extends Disposable {
widgetOptions,
{
disabled,
defaultLabel: 'Mixed format'
defaultLabel: t('Mixed format')
}
),
testId('widget-select')
@@ -286,7 +287,7 @@ export class FieldBuilder extends Disposable {
// If we are waiting for a server response
use(this.isCallPending),
menuCssClass: cssTypeSelectMenu.className,
defaultLabel: 'Mixed types',
defaultLabel: t('Mixed types'),
renderOptionArgs: (op) => {
if (['Ref', 'RefList'].includes(selectType.get())) {
// Don't show tip if a reference column type is already selected.
@@ -340,7 +341,7 @@ export class FieldBuilder extends Disposable {
// If we selected multiple empty/formula columns, make the change for all of them.
if (this.field.viewSection.peek().selectedFields.peek().length > 1 &&
['formula', 'empty'].indexOf(this.field.viewSection.peek().columnsBehavior.peek())) {
return this.gristDoc.docData.bundleActions("Changing multiple column types", () =>
return this.gristDoc.docData.bundleActions(t("Changing multiple column types"), () =>
Promise.all(this.field.viewSection.peek().selectedFields.peek().map(f =>
f.column.peek().type.setAndSave(calculatedType)
))).catch(reportError);
@@ -369,7 +370,7 @@ export class FieldBuilder extends Disposable {
return use(this.origColumn.disableModifyBase) || use(this.field.config.multiselect);
});
return [
cssLabel('DATA FROM TABLE',
cssLabel(t('DATA FROM TABLE'),
!this._showRefConfigPopup.peek() ? null : this.gristDoc.behavioralPromptsManager.attachTip(
'referenceColumnsConfig',
{
@@ -417,7 +418,7 @@ export class FieldBuilder extends Disposable {
}
}),
kf.row(
15, kf.label('Apply Formula to Data'),
15, kf.label(t('Apply Formula to Data')),
3, kf.buttonGroup(
kf.checkButton(transformButton,
dom('span.glyphicon.glyphicon-flash'),
@@ -498,7 +499,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsUseSeparate() {
return this.gristDoc.docData.bundleActions(
`Use separate field settings for ${this.origColumn.colId()}`, () => {
t("Use separate field settings for {{colId}}", { colId: this.origColumn.colId() }), () => {
return Promise.all([
setSaveValue(this.field.widgetOptions, this.field.column().widgetOptions()),
setSaveValue(this.field.visibleCol, this.field.column().visibleCol()),
@@ -510,7 +511,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsSaveAsCommon() {
return this.gristDoc.docData.bundleActions(
`Save field settings for ${this.origColumn.colId()} as common`, () => {
t("Save field settings for {{colId}} as common", { colId: this.origColumn.colId() }), () => {
return Promise.all([
setSaveValue(this.field.column().widgetOptions, this.field.widgetOptions()),
setSaveValue(this.field.column().visibleCol, this.field.visibleCol()),
@@ -525,7 +526,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsRevertToCommon() {
return this.gristDoc.docData.bundleActions(
`Revert field settings for ${this.origColumn.colId()} to common`, () => {
t("Revert field settings for {{colId}} to common", { colId: this.origColumn.colId() }), () => {
return Promise.all([
setSaveValue(this.field.widgetOptions, ''),
setSaveValue(this.field.visibleCol, 0),

View File

@@ -2,6 +2,7 @@ import * as commands from 'app/client/components/commands';
import {Cursor} from 'app/client/components/Cursor';
import {GristDoc} from 'app/client/components/GristDoc';
import {UnsavedChange} from 'app/client/components/UnsavedChanges';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {reportError} from 'app/client/models/errors';
@@ -17,6 +18,8 @@ import {CellPosition} from "app/client/components/CellPosition";
type IEditorConstructor = typeof NewBaseEditor;
const t = makeT('FieldEditor');
/**
* Check if the typed-in value should change the cell without opening the cell editor, and if so,
* saves and returns true. E.g. on typing space, CheckBoxEditor toggles the cell without opening.
@@ -320,7 +323,7 @@ export class FieldEditor extends Disposable {
await editor.prepForSave();
if (this.isDisposed()) {
// We shouldn't normally get disposed here, but if we do, avoid confusing JS errors.
console.warn("Unable to finish saving edited cell"); // tslint:disable-line:no-console
console.warn(t("Unable to finish saving edited cell")); // tslint:disable-line:no-console
return false;
}
@@ -349,7 +352,7 @@ export class FieldEditor extends Disposable {
const value = editor.getCellValue();
if (col.isRealFormula()) {
// tslint:disable-next-line:no-console
console.warn("It should be impossible to save a plain data value into a formula column");
console.warn(t("It should be impossible to save a plain data value into a formula column"));
} else {
// This could still be an isFormula column if it's empty (isEmpty is true), but we don't
// need to toggle isFormula in that case, since the data engine takes care of that.

View File

@@ -1,5 +1,6 @@
import * as AceEditor from 'app/client/components/AceEditor';
import {createGroup} from 'app/client/components/commands';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {colors, testId, theme} from 'app/client/ui2018/cssVars';
@@ -20,6 +21,7 @@ import debounce = require('lodash/debounce');
// How wide to expand the FormulaEditor when an error is shown in it.
const minFormulaErrorWidth = 400;
const t = makeT('FormulaEditor');
export interface IFormulaEditorOptions extends Options {
cssClass?: string;
@@ -293,7 +295,7 @@ export function openFormulaEditor(options: {
const column = options.column ?? options.field?.column();
if (!column) {
throw new Error('Column or field is required');
throw new Error(t('Column or field is required'));
}
// AsyncOnce ensures it's called once even if triggered multiple times.
@@ -338,7 +340,7 @@ export function openFormulaEditor(options: {
const editingFormula = options.editingFormula ?? options?.field?.editingFormula;
if (!editingFormula) {
throw new Error('editingFormula is required');
throw new Error(t('editingFormula is required'));
}
// When formula is empty enter formula-editing mode (highlight formula icons; click on a column inserts its ID).
@@ -393,9 +395,9 @@ export function createFormulaErrorObs(owner: MultiHolder, gristDoc: GristDoc, or
const numErrors = tableData.countErrors(colId) || 0;
errorMessage.set(
(numErrors === 0) ? '' :
(numCells === 1) ? `Error in the cell` :
(numErrors === numCells) ? `Errors in all ${numErrors} cells` :
`Errors in ${numErrors} of ${numCells} cells`
(numCells === 1) ? t(`Error in the cell`) :
(numErrors === numCells) ? t(`Errors in all {{numErrors}} cells`, {numErrors}) :
t(`Errors in {{numErrors}} of {{numCells}} cells`, {numErrors, numCells})
);
} else {
errorMessage.set('');

View File

@@ -1,6 +1,9 @@
import {makeT} from 'app/client/lib/localization';
import {FieldOptions} from 'app/client/widgets/NewBaseEditor';
import {NTextEditor} from 'app/client/widgets/NTextEditor';
const t = makeT('HyperLinkEditor');
/**
* HyperLinkEditor - Is the same NTextEditor but with some placeholder text to help explain
* to the user how links should be formatted.
@@ -8,6 +11,6 @@ import {NTextEditor} from 'app/client/widgets/NTextEditor';
export class HyperLinkEditor extends NTextEditor {
constructor(options: FieldOptions) {
super(options);
this.textInput.setAttribute('placeholder', '[link label] url');
this.textInput.setAttribute('placeholder', t('[link label] url'));
}
}

View File

@@ -1,6 +1,7 @@
/**
* See app/common/NumberFormat for description of options we support.
*/
import {makeT} from 'app/client/lib/localization';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {reportError} from 'app/client/models/errors';
import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
@@ -16,6 +17,7 @@ import {BindableValue, Computed, dom, DomContents, DomElementArg,
import * as LocaleCurrency from 'locale-currency';
const t = makeT('NumericTextBox');
const modeOptions: Array<ISelectorOption<NumMode>> = [
{value: 'currency', label: '$'},
{value: 'decimal', label: ','},
@@ -85,23 +87,23 @@ export class NumericTextBox extends NTextBox {
return [
super.buildConfigDom(),
cssLabel('Number Format'),
cssLabel(t('Number Format')),
cssRow(
dom.autoDispose(holder),
makeButtonSelect(numMode, modeOptions, setMode, disabledStyle, cssModeSelect.cls(''), testId('numeric-mode')),
makeButtonSelect(numSign, signOptions, setSign, disabledStyle, cssSignSelect.cls(''), testId('numeric-sign')),
),
dom.maybe((use) => use(numMode) === 'currency', () => [
cssLabel('Currency'),
cssLabel(t('Currency')),
cssRow(
dom.domComputed(docCurrency, (defaultCurrency) =>
buildCurrencyPicker(holder, currency, setCurrency,
{defaultCurrencyLabel: `Default currency (${defaultCurrency})`, disabled})
{defaultCurrencyLabel: t(`Default currency ({{defaultCurrency}})`, {defaultCurrency}), disabled})
),
testId("numeric-currency")
)
]),
cssLabel('Decimals'),
cssLabel(t('Decimals')),
cssRow(
decimals('min', minDecimals, defaultMin, setMinDecimals, disabled, testId('numeric-min-decimals')),
decimals('max', maxDecimals, defaultMax, setMaxDecimals, disabled, testId('numeric-max-decimals')),

View File

@@ -1,3 +1,4 @@
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
@@ -8,6 +9,9 @@ import {NTextBox} from 'app/client/widgets/NTextBox';
import {isFullReferencingType, isVersions} from 'app/common/gristTypes';
import {Computed, dom, styled} from 'grainjs';
const t = makeT('Reference');
/**
* Reference - The widget for displaying references to another table's records.
*/
@@ -33,14 +37,14 @@ export class Reference extends NTextBox {
icon: 'FieldColumn',
disabled: isFullReferencingType(use(col.type)) || use(col.isTransforming)
}))
.concat([{label: 'Row ID', value: 0, icon: 'FieldColumn'}]);
.concat([{label: t('Row ID'), value: 0, icon: 'FieldColumn'}]);
});
}
public buildConfigDom() {
return [
this.buildTransformConfigDom(),
cssLabel('CELL FORMAT'),
cssLabel(t('CELL FORMAT')),
super.buildConfigDom()
];
}
@@ -48,7 +52,7 @@ export class Reference extends NTextBox {
public buildTransformConfigDom() {
const disabled = Computed.create(null, use => use(this.field.config.multiselect));
return [
cssLabel('SHOW COLUMN'),
cssLabel(t('SHOW COLUMN')),
cssRow(
dom.autoDispose(disabled),
select(this._visibleColRef, this._validCols, {

View File

@@ -36,6 +36,7 @@ export function mergeOptions(options: any, type: string) {
// The names of widgets are used, instead of the actual classes needed, in order to limit
// the spread of dependencies. See ./UserTypeImpl for actual classes.
export const typeDefs: any = {
// TODO : translate labels (can not use classic makeT function)
Any: {
label: 'Any',
icon: 'FieldAny',