trad: make the widgets and the Welcome Tour translatable

trad: make the widgets and the Welcome Tour translatable

feat(translation): create automatisation for synchronize key in other locals than en

trad: add french translations

fix(trad): remove all useless code

fix(trad): convert tab to space indentation

fix(trad): add line to english trads
This commit is contained in:
Camille 2023-01-11 18:57:42 +01:00
parent 6804283603
commit 4befca1c92
19 changed files with 248 additions and 112 deletions

View File

@ -1,3 +1,4 @@
import { makeT } from 'app/client/lib/localization';
import * as commands from 'app/client/components/commands'; import * as commands from 'app/client/components/commands';
import { urlState } from 'app/client/models/gristUrlState'; import { urlState } from 'app/client/models/gristUrlState';
import { IOnBoardingMsg, startOnBoarding } from "app/client/ui/OnBoardingPopups"; 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 { cssLink } from "app/client/ui2018/links";
import { dom, styled } from "grainjs"; import { dom, styled } from "grainjs";
const t = makeT('WelcomeTour');
export const welcomeTour: IOnBoardingMsg[] = [ export const welcomeTour: IOnBoardingMsg[] = [
{ {
title: 'Editing Data', title: t('Editing Data'),
body: () => [ body: () => [
dom('p', dom('p',
'Double-click or hit ', Key(KeyContent('Enter')), ' on a cell to edit it. ', t('Double-click or hit {{enter}} on a cell to edit it. ', {enter: Key(KeyContent(t('Enter')))}),
'Start with ', Key(KeyStrong('=')), ' to enter a formula.' t('Start with {{equal}} to enter a formula.', { equal: Key(KeyStrong('=')) }))
)
], ],
selector: '.field_clip', selector: '.field_clip',
placement: 'bottom', placement: 'bottom',
}, },
{ {
selector: '.tour-creator-panel', selector: '.tour-creator-panel',
title: 'Configuring your document', title: t('Configuring your document'),
body: () => [ body: () => [
dom('p', dom('p',
'Toggle the ', dom('em', 'creator panel'), ' to format columns, ', t('Toggle the {{creatorPanel}} to format columns, ', {creatorPanel: dom('em', t('creator panel'))}),
'convert to card view, select data, and more.' t('convert to card view, select data, and more.')
) )
], ],
placement: 'left', placement: 'left',
@ -32,50 +34,52 @@ export const welcomeTour: IOnBoardingMsg[] = [
}, },
{ {
selector: '.tour-type-selector', selector: '.tour-type-selector',
title: 'Customizing columns', title: t('Customizing columns'),
body: () => [ body: () => [
dom('p', 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', 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', placement: 'right',
}, },
{ {
selector: '.tour-add-new', selector: '.tour-add-new',
title: 'Building up', title: t('Building up'),
body: () => [ 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', placement: 'right',
}, },
{ {
selector: '.tour-share-icon', selector: '.tour-share-icon',
title: 'Sharing', title: t('Sharing'),
body: () => [ 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', placement: 'bottom',
cropPadding: true, cropPadding: true,
}, },
{ {
selector: '.tour-help-center', selector: '.tour-help-center',
title: 'Flying higher', title: t('Flying higher'),
body: () => [ 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', placement: 'right',
}, },
{ {
selector: '.tour-welcome', selector: '.tour-welcome',
title: 'Welcome to Grist!', title: t('Welcome to Grist!'),
body: () => [ body: () => [
dom('p', 'Browse our ', dom('p', t("Browse our {{templateLibrary}} to discover what's possible and get inspired.",
cssLink({target: '_blank', href: urlState().makeUrl({homePage: "templates"})}, {
'template library', cssInlineIcon('FieldLink')), templateLibrary: cssLink({ target: '_blank', href: urlState().makeUrl({ homePage: "templates" }) },
"to discover what's possible and get inspired." t('template library'), cssInlineIcon('FieldLink'))
), }
)),
], ],
showHasModal: true, showHasModal: true,
} }

View File

@ -1,3 +1,4 @@
import { makeT } from 'app/client/lib/localization';
import {allCommands} from 'app/client/components/commands'; import {allCommands} from 'app/client/components/commands';
import {GristDoc} from 'app/client/components/GristDoc'; import {GristDoc} from 'app/client/components/GristDoc';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; 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 {ConditionalStyle} from 'app/client/widgets/ConditionalStyle';
import {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs'; import {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs';
const t = makeT('CellStyle');
export class CellStyle extends Disposable { export class CellStyle extends Disposable {
constructor( constructor(
@ -20,8 +23,8 @@ export class CellStyle extends Disposable {
public buildDom(): DomContents { public buildDom(): DomContents {
return [ return [
cssLine( cssLine(
cssLabel('CELL STYLE'), cssLabel(t('CELL STYLE')),
cssButton('Open row styles', dom.on('click', allCommands.viewTabOpen.run)), cssButton(t('Open row styles'), dom.on('click', allCommands.viewTabOpen.run)),
), ),
cssRow( cssRow(
dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => { dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => {
@ -58,12 +61,12 @@ export class CellStyle extends Disposable {
}, { }, {
onSave: () => options.save(), onSave: () => options.save(),
onRevert: () => options.revert(), 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

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

View File

@ -1,3 +1,4 @@
import {makeT} from 'app/client/lib/localization';
import {GristDoc} from 'app/client/components/GristDoc'; import {GristDoc} from 'app/client/components/GristDoc';
import {ColumnRec} from 'app/client/models/DocModel'; import {ColumnRec} from 'app/client/models/DocModel';
import {KoSaveableObservable} from 'app/client/models/modelUtil'; import {KoSaveableObservable} from 'app/client/models/modelUtil';
@ -18,6 +19,7 @@ import {Computed, Disposable, dom, DomContents, makeTestId, Observable, styled}
import debounce = require('lodash/debounce'); import debounce = require('lodash/debounce');
const testId = makeTestId('test-widget-style-'); const testId = makeTestId('test-widget-style-');
const t = makeT('ConditionalStyle');
export class ConditionalStyle extends Disposable { export class ConditionalStyle extends Disposable {
// Holds data from currently selected record (holds data only when this field has conditional styles). // 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' }, { style: 'margin-top: 16px' },
withInfoTooltip( withInfoTooltip(
textButton( textButton(
'Add conditional style', t('Add conditional style'),
testId('add-conditional-style'), testId('add-conditional-style'),
dom.on('click', () => this._ruleOwner.addEmptyRule()), dom.on('click', () => this._ruleOwner.addEmptyRule()),
dom.prop('disabled', this._disabled), dom.prop('disabled', this._disabled),
), ),
(this._label === 'Row Style' (this._label === t('Row Style')
? GristTooltips.addRowConditionalStyle() ? GristTooltips.addRowConditionalStyle()
: GristTooltips.addColumnConditionalStyle() : GristTooltips.addColumnConditionalStyle()
), ),
@ -113,8 +115,8 @@ export class ConditionalStyle extends Disposable {
const errorMessage = Computed.create(owner, use => { const errorMessage = Computed.create(owner, use => {
const value = use(currentValue); const value = use(currentValue);
return (!use(hasError) ? '' : return (!use(hasError) ? '' :
isRaisedException(value) ? 'Error in style rule' : isRaisedException(value) ? t('Error in style rule') :
'Rule must return True or False'); t('Rule must return True or False'));
}); });
return dom('div', return dom('div',
testId(`conditional-rule-${ruleIndex}`), testId(`conditional-rule-${ruleIndex}`),
@ -153,7 +155,7 @@ export class ConditionalStyle extends Disposable {
) )
), ),
cssRow( cssRow(
textButton('Add another rule', textButton(t('Add another rule'),
dom.on('click', () => this._ruleOwner.addEmptyRule()), dom.on('click', () => this._ruleOwner.addEmptyRule()),
testId('add-another-rule'), testId('add-another-rule'),
dom.prop('disabled', use => this._disabled && use(this._disabled)) 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 {ACSelectItem, buildACSelect} from "app/client/lib/ACSelect";
import {Computed, IDisposableOwner, Observable} from "grainjs"; import {Computed, IDisposableOwner, Observable} from "grainjs";
import {ACIndexImpl} from "app/client/lib/ACIndex"; import {ACIndexImpl} from "app/client/lib/ACIndex";
import {testId} from 'app/client/ui2018/cssVars'; import {testId} from 'app/client/ui2018/cssVars';
import {currencies} from 'app/common/Locales'; import {currencies} from 'app/common/Locales';
const t = makeT('CurrencyPicker');
interface CurrencyPickerOptions { interface CurrencyPickerOptions {
// The label to use in the select menu for the default option. // The label to use in the select menu for the default option.
defaultCurrencyLabel: string; defaultCurrencyLabel: string;
@ -40,7 +43,7 @@ export function buildCurrencyPicker(
save(_, item: ACSelectItem | undefined) { save(_, item: ACSelectItem | undefined) {
// Save only if we have found a match // Save only if we have found a match
if (!item) { if (!item) {
throw new Error("Invalid currency"); throw new Error(t("Invalid currency"));
} }
// For default value, return undefined to use default currency for document. // For default value, return undefined to use default currency for document.
onSave(item.value === defaultCurrencyLabel ? undefined : item.value); onSave(item.value === defaultCurrencyLabel ? undefined : item.value);

View File

@ -1,4 +1,5 @@
import {GristDoc} from 'app/client/components/GristDoc'; import {GristDoc} from 'app/client/components/GristDoc';
import {makeT} from 'app/client/lib/localization';
import {FocusLayer} from 'app/client/lib/FocusLayer'; import {FocusLayer} from 'app/client/lib/FocusLayer';
import {createObsArray} from 'app/client/lib/koArrayWrap'; import {createObsArray} from 'app/client/lib/koArrayWrap';
import {localStorageBoolObs} from 'app/client/lib/localStorageObs'; import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
@ -35,6 +36,7 @@ import maxSize from 'popper-max-size-modifier';
import flatMap = require('lodash/flatMap'); import flatMap = require('lodash/flatMap');
const testId = makeTestId('test-discussion-'); const testId = makeTestId('test-discussion-');
const t = makeT('DiscussionEditor');
const COMMENTS_LIMIT = 200; const COMMENTS_LIMIT = 200;
interface DiscussionPopupProps { interface DiscussionPopupProps {
@ -68,7 +70,7 @@ export class CellWithComments extends Disposable implements ICellView {
public async reply(comment: CellRec, text: string): Promise<void> { public async reply(comment: CellRec, text: string): Promise<void> {
const author = commentAuthor(this.gristDoc); 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([ this.gristDoc.docModel.cells.sendTableAction([
"AddRecord", "AddRecord",
null, 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, text: this._newText,
onSave: () => this.props.onSave(this._newText.get()), onSave: () => this.props.onSave(this._newText.get()),
onCancel: () => this.props.closeClicked?.(), onCancel: () => this.props.closeClicked?.(),
editorArgs: [{placeholder: 'Write a comment'}], editorArgs: [{placeholder: t('Write a comment')}],
mainButton: 'Comment', mainButton: t('Comment'),
buttons: ['Cancel'], buttons: [t('Cancel')],
args: [testId('editor-start')] args: [testId('editor-start')]
})); }));
} }
@ -274,7 +276,7 @@ class CellWithCommentsView extends Disposable implements IDomComponent {
public buildDom() { public buildDom() {
return cssTopic( 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), cssTopic.cls('-panel', this.props.panel),
domOnCustom(CommentView.EDIT, (s: CommentView) => this._onEditComment(s)), domOnCustom(CommentView.EDIT, (s: CommentView) => this._onEditComment(s)),
domOnCustom(CommentView.CANCEL, (s: CommentView) => this._onCancelEdit()), domOnCustom(CommentView.CANCEL, (s: CommentView) => this._onCancelEdit()),
@ -358,7 +360,7 @@ class CellWithCommentsView extends Disposable implements IDomComponent {
onSave: () => this._save(), onSave: () => this._save(),
onCancel: () => this.props.closeClicked?.(), onCancel: () => this.props.closeClicked?.(),
mainButton: 'Send', mainButton: 'Send',
editorArgs: [{placeholder: 'Comment'}], editorArgs: [{placeholder: t('Comment')}],
args: [testId('editor-add')] args: [testId('editor-add')]
})); }));
} }
@ -464,8 +466,8 @@ class CommentView extends Disposable {
const text = Observable.create(owner, comment.text.peek() ?? ''); const text = Observable.create(owner, comment.text.peek() ?? '');
return dom.create(CommentEntry, { return dom.create(CommentEntry, {
text, text,
mainButton: 'Save', mainButton: t('Save'),
buttons: ['Cancel'], buttons: [t('Cancel')],
onSave: async () => { onSave: async () => {
const value = text.get(); const value = text.get();
text.set(""); text.set("");
@ -503,7 +505,7 @@ class CommentView extends Disposable {
dom.maybe(use => !use(this.isEditing) && !this.props.isReply && !use(comment.resolved), dom.maybe(use => !use(this.isEditing) && !this.props.isReply && !use(comment.resolved),
() => dom.domComputed(use => { () => dom.domComputed(use => {
if (!use(this.replying)) { if (!use(this.replying)) {
return cssReplyButton(icon('Message'), 'Reply', return cssReplyButton(icon('Message'), t('Reply'),
testId('comment-reply-button'), testId('comment-reply-button'),
dom.on('click', withStop(() => this.replying.set(true))), dom.on('click', withStop(() => this.replying.set(true))),
dom.style('margin-left', use2 => use2(this._hasReplies) ? '16px' : '0px'), dom.style('margin-left', use2 => use2(this._hasReplies) ? '16px' : '0px'),
@ -513,8 +515,8 @@ class CommentView extends Disposable {
return dom.create(CommentEntry, { return dom.create(CommentEntry, {
text, text,
args: [dom.style('margin-top', '8px'), testId('editor-reply')], args: [dom.style('margin-top', '8px'), testId('editor-reply')],
mainButton: 'Reply', mainButton: t('Reply'),
buttons: ['Cancel'], buttons: [t('Cancel')],
onSave: async () => { onSave: async () => {
const value = text.get(); const value = text.get();
this.replying.set(false); this.replying.set(false);
@ -522,7 +524,7 @@ class CommentView extends Disposable {
}, },
onCancel: () => this.replying.set(false), onCancel: () => this.replying.set(false),
onClick: (button) => { onClick: (button) => {
if (button === 'Cancel') { if (button === t('Cancel')) {
this.replying.set(false); this.replying.set(false);
} }
}, },
@ -538,7 +540,7 @@ class CommentView extends Disposable {
testId('comment-resolved'), testId('comment-resolved'),
icon('FieldChoice'), icon('FieldChoice'),
cssResolvedText(dom.text( cssResolvedText(dom.text(
`Marked as resolved` t(`Marked as resolved`)
))); )));
}), }),
]), ]),
@ -554,23 +556,23 @@ class CommentView extends Disposable {
!canResolve ? null : !canResolve ? null :
menuItem( menuItem(
() => this.props.topic.resolve(this.props.comment), () => this.props.topic.resolve(this.props.comment),
'Resolve' t('Resolve')
), ),
!comment.resolved() ? null : !comment.resolved() ? null :
menuItem( menuItem(
() => this.props.topic.open(comment), () => this.props.topic.open(comment),
'Open' t('Open')
), ),
menuItem( menuItem(
() => this.props.topic.remove(comment), () => this.props.topic.remove(comment),
'Remove', t('Remove'),
dom.cls('disabled', use => { dom.cls('disabled', use => {
return currentUser !== use(comment.userRef); return currentUser !== use(comment.userRef);
}) })
), ),
menuItem( menuItem(
() => this._edit(), () => this._edit(),
'Edit', t('Edit'),
dom.cls('disabled', use => { dom.cls('disabled', use => {
return currentUser !== use(comment.userRef); return currentUser !== use(comment.userRef);
}) })
@ -605,7 +607,7 @@ class CommentEntry extends Disposable {
public buildDom() { public buildDom() {
const text = this.props.text; const text = this.props.text;
const clickBuilder = (button: string) => dom.on('click', () => { const clickBuilder = (button: string) => dom.on('click', () => {
if (button === "Cancel") { if (button === t("Cancel")) {
this.props.onCancel?.(); this.props.onCancel?.();
} else { } else {
this.props.onClick?.(button); this.props.onClick?.(button);
@ -699,9 +701,9 @@ export class DiscussionPanel extends Disposable implements IDomComponent {
const tables = Computed.create(owner, use => { const tables = Computed.create(owner, use => {
// Filter out those tables that are not available by ACL. // Filter out those tables that are not available by ACL.
if (use(this._currentPageKo)) { 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 { } 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 allDiscussions = Computed.create(owner, use => {
const list = flatMap(flatMap(use(tables).map(t => { const list = flatMap(flatMap(use(tables).map(tb => {
const columns = use(use(t.columns).getObservable()); const columns = use(use(tb.columns).getObservable());
const dList = columns.map(col => use(use(col.cells).getObservable()) const dList = columns.map(col => use(use(col.cells).getObservable())
.filter(c => use(c.root) && use(c.type) === CellInfoType.COMMENT)); .filter(c => use(c.root) && use(c.type) === CellInfoType.COMMENT));
return dList; return dList;
@ -818,9 +820,9 @@ export class DiscussionPanel extends Disposable implements IDomComponent {
testId('panel-menu'), testId('panel-menu'),
menu(() => { menu(() => {
return [cssDropdownMenu( return [cssDropdownMenu(
labeledSquareCheckbox(this._onlyMine, "Only my threads", testId('my-threads')), labeledSquareCheckbox(this._onlyMine, t("Only my threads"), testId('my-threads')),
labeledSquareCheckbox(this._currentPage, "Only current page", testId('only-page')), labeledSquareCheckbox(this._currentPage, t("Only current page"), testId('only-page')),
labeledSquareCheckbox(this._resolved, "Show resolved comments", testId('show-resolved')), labeledSquareCheckbox(this._resolved, t("Show resolved comments"), testId('show-resolved')),
)]; )];
}, {placement: 'bottom-start'}), }, {placement: 'bottom-start'}),
dom.on('click', stopPropagation) 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 {ITooltipControl, showTooltip, tooltipCloseButton} from 'app/client/ui/tooltips';
import {colors, testId} from 'app/client/ui2018/cssVars'; import {colors, testId} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons'; import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links'; import {cssLink} from 'app/client/ui2018/links';
import {dom, styled} from 'grainjs'; import {dom, styled} from 'grainjs';
const t = makeT('DiscussionEditor');
export function showTooltipToCreateFormula(editorDom: HTMLElement, convert: () => void) { export function showTooltipToCreateFormula(editorDom: HTMLElement, convert: () => void) {
function buildTooltip(ctl: ITooltipControl) { function buildTooltip(ctl: ITooltipControl) {
return cssConvertTooltip(icon('Convert'), return cssConvertTooltip(icon('Convert'),
cssLink('Convert column to formula', cssLink(t('Convert column to formula'),
dom.on('mousedown', (ev) => { ev.preventDefault(); convert(); }), dom.on('mousedown', (ev) => { ev.preventDefault(); convert(); }),
testId('editor-tooltip-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 kd from 'app/client/lib/koDom';
import * as kf from 'app/client/lib/koForm'; import * as kf from 'app/client/lib/koForm';
import * as koUtil from 'app/client/lib/koUtil'; import * as koUtil from 'app/client/lib/koUtil';
import { makeT } from 'app/client/lib/localization';
import { reportError } from 'app/client/models/AppModel'; import { reportError } from 'app/client/models/AppModel';
import { DataRowModel } from 'app/client/models/DataRowModel'; import { DataRowModel } from 'app/client/models/DataRowModel';
import { ColumnRec, DocModel, ViewFieldRec } from 'app/client/models/DocModel'; import { ColumnRec, DocModel, ViewFieldRec } from 'app/client/models/DocModel';
@ -38,7 +39,7 @@ import * as ko from 'knockout';
import * as _ from 'underscore'; import * as _ from 'underscore';
const testId = makeTestId('test-fbuilder-'); const testId = makeTestId('test-fbuilder-');
const t = makeT('FieldBuilder');
// Creates a FieldBuilder object for each field in viewFields // 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)); defaultWidget.onWrite((value) => this.field.config.widget(value));
const disabled = Computed.create(null, use => !use(this.field.config.sameWidgets)); const disabled = Computed.create(null, use => !use(this.field.config.sameWidgets));
return [ return [
cssLabel('CELL FORMAT'), cssLabel(t('CELL FORMAT')),
cssRow( cssRow(
grainjsDom.autoDispose(defaultWidget), grainjsDom.autoDispose(defaultWidget),
widgetOptions.length <= 2 ? widgetOptions.length <= 2 ?
@ -242,7 +243,7 @@ export class FieldBuilder extends Disposable {
widgetOptions, widgetOptions,
{ {
disabled, disabled,
defaultLabel: 'Mixed format' defaultLabel: t('Mixed format')
} }
), ),
testId('widget-select') testId('widget-select')
@ -286,7 +287,7 @@ export class FieldBuilder extends Disposable {
// If we are waiting for a server response // If we are waiting for a server response
use(this.isCallPending), use(this.isCallPending),
menuCssClass: cssTypeSelectMenu.className, menuCssClass: cssTypeSelectMenu.className,
defaultLabel: 'Mixed types', defaultLabel: t('Mixed types'),
renderOptionArgs: (op) => { renderOptionArgs: (op) => {
if (['Ref', 'RefList'].includes(selectType.get())) { if (['Ref', 'RefList'].includes(selectType.get())) {
// Don't show tip if a reference column type is already selected. // 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 we selected multiple empty/formula columns, make the change for all of them.
if (this.field.viewSection.peek().selectedFields.peek().length > 1 && if (this.field.viewSection.peek().selectedFields.peek().length > 1 &&
['formula', 'empty'].indexOf(this.field.viewSection.peek().columnsBehavior.peek())) { ['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 => Promise.all(this.field.viewSection.peek().selectedFields.peek().map(f =>
f.column.peek().type.setAndSave(calculatedType) f.column.peek().type.setAndSave(calculatedType)
))).catch(reportError); ))).catch(reportError);
@ -369,7 +370,7 @@ export class FieldBuilder extends Disposable {
return use(this.origColumn.disableModifyBase) || use(this.field.config.multiselect); return use(this.origColumn.disableModifyBase) || use(this.field.config.multiselect);
}); });
return [ return [
cssLabel('DATA FROM TABLE', cssLabel(t('DATA FROM TABLE'),
!this._showRefConfigPopup.peek() ? null : this.gristDoc.behavioralPromptsManager.attachTip( !this._showRefConfigPopup.peek() ? null : this.gristDoc.behavioralPromptsManager.attachTip(
'referenceColumnsConfig', 'referenceColumnsConfig',
{ {
@ -417,7 +418,7 @@ export class FieldBuilder extends Disposable {
} }
}), }),
kf.row( kf.row(
15, kf.label('Apply Formula to Data'), 15, kf.label(t('Apply Formula to Data')),
3, kf.buttonGroup( 3, kf.buttonGroup(
kf.checkButton(transformButton, kf.checkButton(transformButton,
dom('span.glyphicon.glyphicon-flash'), dom('span.glyphicon.glyphicon-flash'),
@ -498,7 +499,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsUseSeparate() { public fieldSettingsUseSeparate() {
return this.gristDoc.docData.bundleActions( 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([ return Promise.all([
setSaveValue(this.field.widgetOptions, this.field.column().widgetOptions()), setSaveValue(this.field.widgetOptions, this.field.column().widgetOptions()),
setSaveValue(this.field.visibleCol, this.field.column().visibleCol()), setSaveValue(this.field.visibleCol, this.field.column().visibleCol()),
@ -510,7 +511,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsSaveAsCommon() { public fieldSettingsSaveAsCommon() {
return this.gristDoc.docData.bundleActions( 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([ return Promise.all([
setSaveValue(this.field.column().widgetOptions, this.field.widgetOptions()), setSaveValue(this.field.column().widgetOptions, this.field.widgetOptions()),
setSaveValue(this.field.column().visibleCol, this.field.visibleCol()), setSaveValue(this.field.column().visibleCol, this.field.visibleCol()),
@ -525,7 +526,7 @@ export class FieldBuilder extends Disposable {
public fieldSettingsRevertToCommon() { public fieldSettingsRevertToCommon() {
return this.gristDoc.docData.bundleActions( 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([ return Promise.all([
setSaveValue(this.field.widgetOptions, ''), setSaveValue(this.field.widgetOptions, ''),
setSaveValue(this.field.visibleCol, 0), 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 {Cursor} from 'app/client/components/Cursor';
import {GristDoc} from 'app/client/components/GristDoc'; import {GristDoc} from 'app/client/components/GristDoc';
import {UnsavedChange} from 'app/client/components/UnsavedChanges'; import {UnsavedChange} from 'app/client/components/UnsavedChanges';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel'; import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {reportError} from 'app/client/models/errors'; import {reportError} from 'app/client/models/errors';
@ -17,6 +18,8 @@ import {CellPosition} from "app/client/components/CellPosition";
type IEditorConstructor = typeof NewBaseEditor; 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, * 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. * 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(); await editor.prepForSave();
if (this.isDisposed()) { if (this.isDisposed()) {
// We shouldn't normally get disposed here, but if we do, avoid confusing JS errors. // 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; return false;
} }
@ -349,7 +352,7 @@ export class FieldEditor extends Disposable {
const value = editor.getCellValue(); const value = editor.getCellValue();
if (col.isRealFormula()) { if (col.isRealFormula()) {
// tslint:disable-next-line:no-console // 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 { } else {
// This could still be an isFormula column if it's empty (isEmpty is true), but we don't // 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. // 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 * as AceEditor from 'app/client/components/AceEditor';
import {createGroup} from 'app/client/components/commands'; import {createGroup} from 'app/client/components/commands';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel'; import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {colors, testId, theme} from 'app/client/ui2018/cssVars'; 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. // How wide to expand the FormulaEditor when an error is shown in it.
const minFormulaErrorWidth = 400; const minFormulaErrorWidth = 400;
const t = makeT('FormulaEditor');
export interface IFormulaEditorOptions extends Options { export interface IFormulaEditorOptions extends Options {
cssClass?: string; cssClass?: string;
@ -293,7 +295,7 @@ export function openFormulaEditor(options: {
const column = options.column ?? options.field?.column(); const column = options.column ?? options.field?.column();
if (!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. // 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; const editingFormula = options.editingFormula ?? options?.field?.editingFormula;
if (!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). // 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; const numErrors = tableData.countErrors(colId) || 0;
errorMessage.set( errorMessage.set(
(numErrors === 0) ? '' : (numErrors === 0) ? '' :
(numCells === 1) ? `Error in the cell` : (numCells === 1) ? t(`Error in the cell`) :
(numErrors === numCells) ? `Errors in all ${numErrors} cells` : (numErrors === numCells) ? t(`Errors in all {{numErrors}} cells`, {numErrors}) :
`Errors in ${numErrors} of ${numCells} cells` t(`Errors in {{numErrors}} of {{numCells}} cells`, {numErrors, numCells})
); );
} else { } else {
errorMessage.set(''); errorMessage.set('');

View File

@ -1,6 +1,9 @@
import {makeT} from 'app/client/lib/localization';
import {FieldOptions} from 'app/client/widgets/NewBaseEditor'; import {FieldOptions} from 'app/client/widgets/NewBaseEditor';
import {NTextEditor} from 'app/client/widgets/NTextEditor'; import {NTextEditor} from 'app/client/widgets/NTextEditor';
const t = makeT('HyperLinkEditor');
/** /**
* HyperLinkEditor - Is the same NTextEditor but with some placeholder text to help explain * HyperLinkEditor - Is the same NTextEditor but with some placeholder text to help explain
* to the user how links should be formatted. * to the user how links should be formatted.
@ -8,6 +11,6 @@ import {NTextEditor} from 'app/client/widgets/NTextEditor';
export class HyperLinkEditor extends NTextEditor { export class HyperLinkEditor extends NTextEditor {
constructor(options: FieldOptions) { constructor(options: FieldOptions) {
super(options); 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. * 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 {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {reportError} from 'app/client/models/errors'; import {reportError} from 'app/client/models/errors';
import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
@ -16,6 +17,7 @@ import {BindableValue, Computed, dom, DomContents, DomElementArg,
import * as LocaleCurrency from 'locale-currency'; import * as LocaleCurrency from 'locale-currency';
const t = makeT('NumericTextBox');
const modeOptions: Array<ISelectorOption<NumMode>> = [ const modeOptions: Array<ISelectorOption<NumMode>> = [
{value: 'currency', label: '$'}, {value: 'currency', label: '$'},
{value: 'decimal', label: ','}, {value: 'decimal', label: ','},
@ -85,23 +87,23 @@ export class NumericTextBox extends NTextBox {
return [ return [
super.buildConfigDom(), super.buildConfigDom(),
cssLabel('Number Format'), cssLabel(t('Number Format')),
cssRow( cssRow(
dom.autoDispose(holder), dom.autoDispose(holder),
makeButtonSelect(numMode, modeOptions, setMode, disabledStyle, cssModeSelect.cls(''), testId('numeric-mode')), makeButtonSelect(numMode, modeOptions, setMode, disabledStyle, cssModeSelect.cls(''), testId('numeric-mode')),
makeButtonSelect(numSign, signOptions, setSign, disabledStyle, cssSignSelect.cls(''), testId('numeric-sign')), makeButtonSelect(numSign, signOptions, setSign, disabledStyle, cssSignSelect.cls(''), testId('numeric-sign')),
), ),
dom.maybe((use) => use(numMode) === 'currency', () => [ dom.maybe((use) => use(numMode) === 'currency', () => [
cssLabel('Currency'), cssLabel(t('Currency')),
cssRow( cssRow(
dom.domComputed(docCurrency, (defaultCurrency) => dom.domComputed(docCurrency, (defaultCurrency) =>
buildCurrencyPicker(holder, currency, setCurrency, buildCurrencyPicker(holder, currency, setCurrency,
{defaultCurrencyLabel: `Default currency (${defaultCurrency})`, disabled}) {defaultCurrencyLabel: t(`Default currency ({{defaultCurrency}})`, {defaultCurrency}), disabled})
), ),
testId("numeric-currency") testId("numeric-currency")
) )
]), ]),
cssLabel('Decimals'), cssLabel(t('Decimals')),
cssRow( cssRow(
decimals('min', minDecimals, defaultMin, setMinDecimals, disabled, testId('numeric-min-decimals')), decimals('min', minDecimals, defaultMin, setMinDecimals, disabled, testId('numeric-min-decimals')),
decimals('max', maxDecimals, defaultMax, setMaxDecimals, disabled, testId('numeric-max-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 {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; 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 {isFullReferencingType, isVersions} from 'app/common/gristTypes';
import {Computed, dom, styled} from 'grainjs'; import {Computed, dom, styled} from 'grainjs';
const t = makeT('Reference');
/** /**
* Reference - The widget for displaying references to another table's records. * Reference - The widget for displaying references to another table's records.
*/ */
@ -33,14 +37,14 @@ export class Reference extends NTextBox {
icon: 'FieldColumn', icon: 'FieldColumn',
disabled: isFullReferencingType(use(col.type)) || use(col.isTransforming) 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() { public buildConfigDom() {
return [ return [
this.buildTransformConfigDom(), this.buildTransformConfigDom(),
cssLabel('CELL FORMAT'), cssLabel(t('CELL FORMAT')),
super.buildConfigDom() super.buildConfigDom()
]; ];
} }
@ -48,7 +52,7 @@ export class Reference extends NTextBox {
public buildTransformConfigDom() { public buildTransformConfigDom() {
const disabled = Computed.create(null, use => use(this.field.config.multiselect)); const disabled = Computed.create(null, use => use(this.field.config.multiselect));
return [ return [
cssLabel('SHOW COLUMN'), cssLabel(t('SHOW COLUMN')),
cssRow( cssRow(
dom.autoDispose(disabled), dom.autoDispose(disabled),
select(this._visibleColRef, this._validCols, { 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 names of widgets are used, instead of the actual classes needed, in order to limit
// the spread of dependencies. See ./UserTypeImpl for actual classes. // the spread of dependencies. See ./UserTypeImpl for actual classes.
export const typeDefs: any = { export const typeDefs: any = {
// TODO : translate labels (can not use classic makeT function)
Any: { Any: {
label: 'Any', label: 'Any',
icon: 'FieldAny', icon: 'FieldAny',

View File

@ -666,7 +666,7 @@
"Current field ": "Aktuelles Feld ", "Current field ": "Aktuelles Feld ",
"OK": "OK" "OK": "OK"
}, },
"TypeTransformation": { "TypeTransform": {
"Apply": "Anwenden", "Apply": "Anwenden",
"Cancel": "Abbrechen", "Cancel": "Abbrechen",
"Preview": "Vorschau", "Preview": "Vorschau",

View File

@ -27,17 +27,21 @@
"Permission to view Access Rules": "Permission to view Access Rules", "Permission to view Access Rules": "Permission to view Access Rules",
"Permissions": "Permissions", "Permissions": "Permissions",
"Remove column {{- colId }} from {{- tableId }} rules": "Remove column {{- colId }} from {{- tableId }} rules", "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", "Remove {{- name }} user attribute": "Remove {{- name }} user attribute",
"Remove {{- tableId }} rules": "Remove {{- tableId }} rules",
"Reset": "Reset", "Reset": "Reset",
"Rules for table ": "Rules for table ", "Rules for table ": "Rules for table ",
"Save": "Save", "Save": "Save",
"Saved": "Saved", "Saved": "Saved",
"Seed rules": "Seed rules",
"Special Rules": "Special Rules", "Special Rules": "Special Rules",
"Type a message...": "Type a message…", "Type a message...": "Type a message…",
"User Attributes": "User Attributes", "User Attributes": "User Attributes",
"View As": "View As", "View As": "View As",
<<<<<<< HEAD
"Seed rules": "Seed rules", "Seed rules": "Seed rules",
=======
>>>>>>> trad: make the widgets and the Welcome Tour translatable
"When adding table rules, automatically add a rule to grant OWNER full access.": "When adding table rules, automatically add a rule to grant OWNER full access." "When adding table rules, automatically add a rule to grant OWNER full access.": "When adding table rules, automatically add a rule to grant OWNER full access."
}, },
"AccountPage": { "AccountPage": {
@ -70,11 +74,6 @@
"Switch Accounts": "Switch Accounts", "Switch Accounts": "Switch Accounts",
"Toggle Mobile Mode": "Toggle Mobile Mode" "Toggle Mobile Mode": "Toggle Mobile Mode"
}, },
"ViewAsDropdown": {
"View As": "View As",
"Users from table": "Users from table",
"Example Users": "Example Users"
},
"ActionLog": { "ActionLog": {
"Action Log failed to load": "Action Log failed to load", "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}}", "Column {{colId}} was subsequently removed in action #{{action.actionNum}}": "Column {{colId}} was subsequently removed in action #{{action.actionNum}}",
@ -129,6 +128,13 @@
"Reset {{count}} entire columns_one": "Reset entire column", "Reset {{count}} entire columns_one": "Reset entire column",
"Reset {{count}} entire columns_other": "Reset {{count}} entire columns" "Reset {{count}} entire columns_other": "Reset {{count}} entire columns"
}, },
"CellStyle": {
"CELL STYLE": "CELL STYLE",
"Cell Style": "Cell Style",
"Default cell style": "Default cell style",
"Mixed style": "Mixed style",
"Open row styles": "Open row styles"
},
"ChartView": { "ChartView": {
"Create separate series for each value of the selected column.": "Create separate series for each value of the selected column.", "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 a series for the length of error bars.": "Each Y series is followed by a series for the length of error bars.",
@ -137,6 +143,9 @@
"Toggle chart aggregation": "Toggle chart aggregation", "Toggle chart aggregation": "Toggle chart aggregation",
"selected new group data columns": "selected new group data columns" "selected new group data columns": "selected new group data columns"
}, },
"ChoiceTextBox": {
"CHOICES": "CHOICES"
},
"CodeEditorPanel": { "CodeEditorPanel": {
"Access denied": "Access denied", "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." "Code View is available only when you have full document access.": "Code View is available only when you have full document access."
@ -150,20 +159,30 @@
"All": "All", "All": "All",
"All Except": "All Except", "All Except": "All Except",
"All Shown": "All Shown", "All Shown": "All Shown",
"End": "End",
"Filter by Range": "Filter by Range", "Filter by Range": "Filter by Range",
"Future Values": "Future Values", "Future Values": "Future Values",
"Max": "Max",
"Min": "Min",
"No matching values": "No matching values", "No matching values": "No matching values",
"None": "None", "None": "None",
"Min": "Min",
"Max": "Max",
"Start": "Start",
"End": "End",
"Other Matching": "Other Matching", "Other Matching": "Other Matching",
"Other Non-Matching": "Other Non-Matching", "Other Non-Matching": "Other Non-Matching",
"Other Values": "Other Values", "Other Values": "Other Values",
"Others": "Others", "Others": "Others",
"Search": "Search", "Search": "Search",
"Search values": "Search values" "Search values": "Search values",
"Start": "Start"
},
"ConditionalStyle": {
"Add another rule": "Add another rule",
"Add conditional style": "Add conditional style",
"Error in style rule": "Error in style rule",
"Row Style": "Row Style",
"Rule must return True or False": "Rule must return True or False"
},
"CurrencyPicker": {
"Invalid currency": "Invalid currency"
}, },
"CustomSectionConfig": { "CustomSectionConfig": {
" (optional)": " (optional)", " (optional)": " (optional)",
@ -191,6 +210,24 @@
"Table ID copied to clipboard": "Table ID copied to clipboard", "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" "You do not have edit access to this document": "You do not have edit access to this document"
}, },
"DiscussionEditor": {
"Cancel": "Cancel",
"Comment": "Comment",
"Edit": "Edit",
"Marked as resolved": "Marked as resolved",
"Only current page": "Only current page",
"Only my threads": "Only my threads",
"Open": "Open",
"Remove": "Remove",
"Reply": "Reply",
"Reply to a comment": "Reply to a comment",
"Resolve": "Resolve",
"Save": "Save",
"Show resolved comments": "Show resolved comments",
"Showing last {{nb}} comments": "Showing last {{nb}} comments",
"Started discussion": "Started discussion",
"Write a comment": "Write a comment"
},
"DocHistory": { "DocHistory": {
"Activity": "Activity", "Activity": "Activity",
"Beta": "Beta", "Beta": "Beta",
@ -291,6 +328,9 @@
"Name for new table": "Name for new table", "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." "Only the document default access rules will apply to the copy.": "Only the document default access rules will apply to the copy."
}, },
"EditorTooltip": {
"Convert column to formula": "Convert column to formula"
},
"ExampleInfo": { "ExampleInfo": {
"Afterschool Program": "Afterschool Program", "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 link data, and create high-productivity layouts.": "Check out our related tutorial for how to link data, and create high-productivity layouts.",
@ -305,6 +345,17 @@
"Welcome to the Investment Research template": "Welcome to the Investment Research 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" "Welcome to the Lightweight CRM template": "Welcome to the Lightweight CRM template"
}, },
"FieldBuilder": {
"Apply Formula to Data": "Apply Formula to Data",
"CELL FORMAT": "CELL FORMAT",
"Changing multiple column types": "Changing multiple column types",
"DATA FROM TABLE": "DATA FROM TABLE",
"Mixed format": "Mixed format",
"Mixed types": "Mixed types",
"Revert field settings for {{colId}} to common": "Revert field settings for {{colId}} to common",
"Save field settings for {{colId}} as common": "Save field settings for {{colId}} as common",
"Use separate field settings for {{colId}}": "Use separate field settings for {{colId}}"
},
"FieldConfig": { "FieldConfig": {
"COLUMN BEHAVIOR": "COLUMN BEHAVIOR", "COLUMN BEHAVIOR": "COLUMN BEHAVIOR",
"COLUMN LABEL AND ID": "COLUMN LABEL AND ID", "COLUMN LABEL AND ID": "COLUMN LABEL AND ID",
@ -326,6 +377,10 @@
"Set trigger formula": "Set trigger formula", "Set trigger formula": "Set trigger formula",
"TRIGGER FORMULA": "TRIGGER FORMULA" "TRIGGER FORMULA": "TRIGGER FORMULA"
}, },
"FieldEditor": {
"It should be impossible to save a plain data value into a formula column": "It should be impossible to save a plain data value into a formula column",
"Unable to finish saving edited cell": "Unable to finish saving edited cell"
},
"FieldMenus": { "FieldMenus": {
"Revert to common settings": "Revert to common settings", "Revert to common settings": "Revert to common settings",
"Save as common settings": "Save as common settings", "Save as common settings": "Save as common settings",
@ -340,6 +395,13 @@
"SearchColumns": "Search columns", "SearchColumns": "Search columns",
"Search Columns": "Search Columns" "Search Columns": "Search Columns"
}, },
"FormulaEditor": {
"Column or field is required": "Column or field is required",
"Error in the cell": "Error in the cell",
"Errors in all {{numErrors}} cells": "Errors in all {{numErrors}} cells",
"Errors in {{numErrors}} of {{numCells}} cells": "Errors in {{numErrors}} of {{numCells}} cells",
"editingFormula is required": "editingFormula is required"
},
"GridOptions": { "GridOptions": {
"Grid Options": "Grid Options", "Grid Options": "Grid Options",
"Horizontal Gridlines": "Horizontal Gridlines", "Horizontal Gridlines": "Horizontal Gridlines",
@ -417,6 +479,9 @@
"Workspace will be moved to Trash.": "Workspace will be moved to Trash.", "Workspace will be moved to Trash.": "Workspace will be moved to Trash.",
"Workspaces": "Workspaces" "Workspaces": "Workspaces"
}, },
"HyperLinkEditor": {
"[link label] url": "[link label] url"
},
"Importer": { "Importer": {
"Merge rows that match these fields:": "Merge rows that match these fields:", "Merge rows that match these fields:": "Merge rows that match these fields:",
"Select fields to match on": "Select fields to match on", "Select fields to match on": "Select fields to match on",
@ -437,8 +502,8 @@
"No destination workspace": "No destination workspace", "No destination workspace": "No destination workspace",
"Organization": "Organization", "Organization": "Organization",
"Original Has Modifications": "Original Has Modifications", "Original Has Modifications": "Original Has Modifications",
"Original Looks Unrelated": "Original Looks Unrelated",
"Original Looks Identical": "Original Looks Identical", "Original Looks Identical": "Original Looks Identical",
"Original Looks Unrelated": "Original Looks Unrelated",
"Overwrite": "Overwrite", "Overwrite": "Overwrite",
"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.": "Replacing the original requires editing rights on the original document.",
"Sign up": "Sign up", "Sign up": "Sign up",
@ -461,6 +526,12 @@
"Report a problem": "Report a problem", "Report a problem": "Report a problem",
"Upgrade Plan": "Upgrade Plan" "Upgrade Plan": "Upgrade Plan"
}, },
"NumericTextBox": {
"Currency": "Currency",
"Decimals": "Decimals",
"Default currency ({{defaultCurrency}})": "Default currency ({{defaultCurrency}})",
"Number Format": "Number Format"
},
"OnBoardingPopups": { "OnBoardingPopups": {
"Finish": "Finish", "Finish": "Finish",
"Next": "Next" "Next": "Next"
@ -496,15 +567,20 @@
}, },
"RecordLayoutEditor": { "RecordLayoutEditor": {
"Add Field": "Add Field", "Add Field": "Add Field",
"Cancel": "Cancel",
"Create New Field": "Create New Field", "Create New Field": "Create New Field",
"Show field {{- label}}": "Show field {{- label}}",
"Save Layout": "Save Layout", "Save Layout": "Save Layout",
"Cancel": "Cancel" "Show field {{- label}}": "Show field {{- label}}"
}, },
"RefSelect": { "RefSelect": {
"Add Column": "Add Column", "Add Column": "Add Column",
"No columns to add": "No columns to add" "No columns to add": "No columns to add"
}, },
"Reference": {
"CELL FORMAT": "CELL FORMAT",
"Row ID": "Row ID",
"SHOW COLUMN": "SHOW COLUMN"
},
"RightPanel": { "RightPanel": {
"CHART TYPE": "CHART TYPE", "CHART TYPE": "CHART TYPE",
"COLUMN TYPE": "COLUMN TYPE", "COLUMN TYPE": "COLUMN TYPE",
@ -577,9 +653,9 @@
"Add Column": "Add Column", "Add Column": "Add Column",
"Empty values last": "Empty values last", "Empty values last": "Empty values last",
"Natural sort": "Natural sort", "Natural sort": "Natural sort",
"Search Columns": "Search columns",
"Update Data": "Update Data", "Update Data": "Update Data",
"Use choice position": "Use choice position", "Use choice position": "Use choice position"
"Search Columns": "Search columns"
}, },
"SortFilterConfig": { "SortFilterConfig": {
"Filter": "FILTER", "Filter": "FILTER",
@ -618,13 +694,6 @@
"Current field ": "Current field ", "Current field ": "Current field ",
"OK": "OK" "OK": "OK"
}, },
"TypeTransformation": {
"Apply": "Apply",
"Cancel": "Cancel",
"Preview": "Preview",
"Revise": "Revise",
"Update formula (Shift+Enter)": "Update formula (Shift+Enter)"
},
"UserManagerModel": { "UserManagerModel": {
"Editor": "Editor", "Editor": "Editor",
"In Full": "In Full", "In Full": "In Full",
@ -642,6 +711,11 @@
"ViewAsBanner": { "ViewAsBanner": {
"UnknownUser": "Unknown User" "UnknownUser": "Unknown User"
}, },
"ViewAsDropdown": {
"Example Users": "Example Users",
"Users from table": "Users from table",
"View As": "View As"
},
"ViewConfigTab": { "ViewConfigTab": {
"Advanced settings": "Advanced settings", "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.", "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.",
@ -779,5 +853,31 @@
"Preview": "Preview", "Preview": "Preview",
"Revise": "Revise", "Revise": "Revise",
"Update formula (Shift+Enter)": "Update formula (Shift+Enter)" "Update formula (Shift+Enter)": "Update formula (Shift+Enter)"
},
"welcomeTour": {
"Add New": "Add New",
"Browse our {{templateLibrary}} to discover what's possible and get inspired.": "Browse our {{templateLibrary}} to discover what's possible and get inspired.",
"Building up": "Building up",
"Configuring your document": "Configuring your document",
"Customizing columns": "Customizing columns",
"Double-click or hit {{enter}} on a cell to edit it. ": "Double-click or hit {{enter}} on a cell to edit it. ",
"Editing Data": "Editing Data",
"Enter": "Enter",
"Flying higher": "Flying higher",
"Help Center": "Help Center",
"Make it relational! Use the {{ref}} type to link tables. ": "Make it relational! Use the {{ref}} type to link tables. ",
"Reference": "Reference",
"Set formatting options, formulas, or column types, such as dates, choices, or attachments. ": "Set formatting options, formulas, or column types, such as dates, choices, or attachments. ",
"Share": "Share",
"Sharing": "Sharing",
"Start with {{equal}} to enter a formula.": "Start with {{equal}} to enter a formula.",
"Toggle the {{creatorPanel}} to format columns, ": "Toggle the {{creatorPanel}} to format columns, ",
"Use the Share button ({{share}}) to share the document or export data.": "Use the Share button ({{share}}) to share the document or export data.",
"Use {{addNew}} to add widgets, pages, or import more data. ": "Use {{addNew}} to add widgets, pages, or import more data. ",
"Use {{helpCenter}} for documentation or questions.": "Use {{helpCenter}} for documentation or questions.",
"Welcome to Grist!": "Welcome to Grist!",
"convert to card view, select data, and more.": "convert to card view, select data, and more.",
"creator panel": "creator panel",
"template library": "template library"
} }
} }

View File

@ -768,7 +768,7 @@
"SelectionSummary": { "SelectionSummary": {
"Copied to clipboard": "Copiado al portapapeles" "Copied to clipboard": "Copiado al portapapeles"
}, },
"TypeTransformation": { "TypeTransform": {
"Apply": "Aplicar", "Apply": "Aplicar",
"Cancel": "Cancelar", "Cancel": "Cancelar",
"Preview": "Vista previa", "Preview": "Vista previa",

View File

@ -610,7 +610,7 @@
"Cancel": "Annuler", "Cancel": "Annuler",
"Close": "Fermer" "Close": "Fermer"
}, },
"TypeTransformation": { "TypeTransform": {
"Apply": "Appliquer", "Apply": "Appliquer",
"Cancel": "Annuler", "Cancel": "Annuler",
"Preview": "Aperçu", "Preview": "Aperçu",

View File

@ -666,7 +666,7 @@
"Current field ": "Campo atual ", "Current field ": "Campo atual ",
"OK": "OK" "OK": "OK"
}, },
"TypeTransformation": { "TypeTransform": {
"Apply": "Aplicar", "Apply": "Aplicar",
"Cancel": "Cancelar", "Cancel": "Cancelar",
"Preview": "Pré-visualização", "Preview": "Pré-visualização",