i18n: userManager translation + some forgotten translations (#557)

* translation: add userManager translation + some forgotten translations
* use '\' caracter for multiple-line strings
pull/576/head
CamilleLegeron 11 months ago committed by GitHub
parent b4b0c805ff
commit 61bd064f73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1178,8 +1178,8 @@ Useful for examples and templates, but not for sensitive data.`),
},
SchemaEdit: {
name: t("Permission to edit document structure"),
description: t("Allow editors to edit structure (e.g. modify and delete tables, columns, " +
"layouts), and to write formulas, which give access to all data regardless of read restrictions."),
description: t("Allow editors to edit structure (e.g. modify and delete tables, columns, \
layouts), and to write formulas, which give access to all data regardless of read restrictions."),
availableBits: ['schemaEdit'],
...schemaEditRules.denyEditors,
},
@ -1323,7 +1323,7 @@ class SpecialSchemaObsRuleSet extends SpecialObsRuleSet {
return dom.maybe(
(use) => use(this._body).every(rule => rule.isBuiltInOrEmpty(use)),
() => cssConditionError({style: 'margin-left: 56px; margin-bottom: 8px;'},
"This default should be changed if editors' access is to be limited. ",
t("This default should be changed if editors' access is to be limited. "),
dom('a', {style: 'color: inherit; text-decoration: underline'},
'Dismiss', dom.on('click', () => this._allowEditors('confirm'))),
testId('rule-schema-edit-warning'),

@ -660,8 +660,9 @@ export class ChartConfig extends GrainJSDisposable {
),
dom.domComputed(this._optionsObj.prop('errorBars'), (value: ChartOptions["errorBars"]) =>
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."))
value === 'separate' ? cssRowHelp(
t("Each Y series is followed by two series, for top and bottom error bars.")
)
: null
),
]),

@ -265,10 +265,9 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
{
explanation: (
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("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})
: 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})

@ -12,6 +12,9 @@ import {TableData} from 'app/common/TableData';
import {BaseFormatter} from 'app/common/ValueFormatter';
import {Computed, Disposable, Observable} from 'grainjs';
import debounce = require('lodash/debounce');
import { makeT } from 'app/client/lib/localization';
const t = makeT('SearchModel');
/**
* SearchModel used to maintain the state of the search UI.
@ -201,7 +204,7 @@ class FinderImpl implements IFinder {
// sort in order that is the same as on the raw data list page,
.sort((a, b) => nativeCompare(a.tableNameDef.peek(), b.tableNameDef.peek()))
// get rawViewSection,
.map(t => t.rawViewSection.peek())
.map(table => table.rawViewSection.peek())
// and test if it isn't an empty record.
.filter(s => Boolean(s.id.peek()));
// Pretend that those are pages.
@ -218,7 +221,7 @@ class FinderImpl implements IFinder {
// Else read all visible pages.
const pages = this._gristDoc.docModel.visibleDocPages.peek();
this._pageStepper.array = pages.map(p => new PageRecWrapper(p, this._openDocPageCB));
this._pageStepper.index = pages.findIndex(t => t.viewRef.peek() === this._gristDoc.activeViewId.get());
this._pageStepper.index = pages.findIndex(page => page.viewRef.peek() === this._gristDoc.activeViewId.get());
if (this._pageStepper.index < 0) { return false; }
}
@ -468,7 +471,7 @@ export class SearchModelImpl extends Disposable implements SearchModel {
this.autoDispose(this.multiPage.addListener(v => { if (v) { this.noMatch.set(false); } }));
this.allLabel = Computed.create(this, use => use(this._gristDoc.activeViewId) === 'data' ?
'Search all tables' : 'Search all pages');
t('Search all tables') : t('Search all pages'));
// Schedule a search restart when user changes pages (otherwise search would resume from the
// previous page that is not shown anymore). Also revert noMatch flag when in single page mode.

@ -131,9 +131,8 @@ export class AccountPage extends Disposable {
),
css.subHeader(t("Two-factor authentication")),
css.description(
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.")
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),
),

@ -78,8 +78,8 @@ export class ApiKey extends Disposable {
dom.maybe((use) => !(use(this._apiKey) || this._anonymous), () => [
basicButton(t("Create"), dom.on('click', () => this._onCreate()), testId('create'),
dom.boolAttr('disabled', this._loading)),
description(t("By generating an API key, you will be able to " +
"make API calls for your own account."), testId('description')),
description(t("By generating an API key, you will be able to \
make API calls for your own account."), testId('description')),
]),
);
}
@ -117,8 +117,8 @@ export class ApiKey extends Disposable {
() => this._onDelete(),
{
explanation: 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?"
"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?"
),
}
);

@ -21,8 +21,8 @@ export async function startDocTour(docData: DocData, docComm: DocComm, onFinishC
const invalidDocTour: IOnBoardingMsg[] = [{
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."),
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,
}];

@ -36,8 +36,8 @@ export const buildExamples = (): IExampleInfo[] => [{
tutorialUrl: 'https://support.getgrist.com/investment-research/',
welcomeCard: {
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."),
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"),
},
}, {

@ -50,8 +50,7 @@ export const GristTooltips: Record<Tooltip, TooltipContentFunc> = {
t('Formulas that trigger in certain cases, and store the calculated value as data.')
),
dom('div',
t('Useful for storing the timestamp or author of a new record, data cleaning, and '
+ 'more.')
t('Useful for storing the timestamp or author of a new record, data cleaning, and more.')
),
dom('div',
cssLink({href: commonUrls.helpTriggerFormulas, target: '_blank'}, t('Learn more.')),
@ -76,8 +75,8 @@ export const GristTooltips: Record<Tooltip, TooltipContentFunc> = {
),
openAccessRules: (...args: DomElementArg[]) => cssTooltipContent(
dom('div',
t('Access rules give you the power to create nuanced rules to determine who can '
+ 'see or edit which parts of your document.')
t('Access rules give you the power to create nuanced rules to determine who can \
see or edit which parts of your document.')
),
dom('div',
cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.')),
@ -126,8 +125,8 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
title: () => t('Reference Columns'),
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div', t('Select the table to link to.')),
dom('div', t('Cells in a reference column always identify an {{entire}} ' +
'record in that table, but you may select which column from that record to show.', {
dom('div', t('Cells in a reference column always identify an {{entire}} \
record in that table, but you may select which column from that record to show.', {
entire: cssItalicizedText(t('entire'))
})),
dom('div',
@ -140,8 +139,8 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
rawDataPage: {
title: () => t('Raw Data page'),
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div', t('The Raw Data page lists all data tables in your document, '
+ 'including summary tables and tables not included in page layouts.')),
dom('div', t('The Raw Data page lists all data tables in your document, \
including summary tables and tables not included in page layouts.')),
dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, t('Learn more.'))),
...args,
),
@ -150,8 +149,8 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
accessRules: {
title: () => t('Access Rules'),
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div', t('Access rules give you the power to create nuanced rules '
+ 'to determine who can see or edit which parts of your document.')),
dom('div', t('Access rules give you the power to create nuanced rules \
to determine who can see or edit which parts of your document.')),
dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.'))),
...args,
),
@ -209,8 +208,7 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
addNew: {
title: () => t('Add New'),
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div', t('Click the Add New button to create new documents or workspaces, '
+ 'or import data.')),
dom('div', t('Click the Add New button to create new documents or workspaces, or import data.')),
...args,
),
deploymentTypes: ['saas'],
@ -219,8 +217,7 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
title: () => t('Anchor Links'),
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div',
t('To make an anchor link that takes the user to a specific cell, click on'
+ ' a row and press {{shortcut}}.',
t('To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.',
{
shortcut: ShortcutKey(ShortcutKeyContent(commands.allCommands.copyLink.humanKeys[0])),
}
@ -235,8 +232,7 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
content: (...args: DomElementArg[]) => cssTooltipContent(
dom('div',
t(
'You can choose one of our pre-made widgets or embed your own ' +
'by providing its full URL.'
'You can choose one of our pre-made widgets or embed your own by providing its full URL.'
),
),
dom('div', cssLink({href: commonUrls.helpCustomWidgets, target: '_blank'}, t('Learn more.'))),

@ -41,8 +41,8 @@ export async function replaceTrunkWithFork(user: FullUser|null, doc: Document, a
if (cmp.summary === 'left' || cmp.summary === 'both') {
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.")}`;
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("Original Looks Unrelated");
buttonText = t("Overwrite");

@ -102,7 +102,7 @@ function shareButton(buttonText: string|null, menuCreateFunc: MenuCreateFunc,
return cssHoverCircle({ style: `margin: 5px;` },
cssTopBarBtn('Share', dom.cls('tour-share-icon')),
menu(menuCreateFunc, {placement: 'bottom-end'}),
hoverTooltip('Share', {key: 'topBarBtnTooltip'}),
hoverTooltip(t('Share'), {key: 'topBarBtnTooltip'}),
testId('tb-share'),
);
} else if (options.buttonAction) {
@ -115,7 +115,7 @@ function shareButton(buttonText: string|null, menuCreateFunc: MenuCreateFunc,
cssShareCircle(
cssShareIcon('Share'),
menu(menuCreateFunc, {placement: 'bottom-end'}),
hoverTooltip('Share', {key: 'topBarBtnTooltip'}),
hoverTooltip(t('Share'), {key: 'topBarBtnTooltip'}),
testId('tb-share'),
),
);
@ -128,7 +128,7 @@ function shareButton(buttonText: string|null, menuCreateFunc: MenuCreateFunc,
cssShareIcon('Share')
),
menu(menuCreateFunc, {placement: 'bottom-end'}),
hoverTooltip('Share', {key: 'topBarBtnTooltip'}),
hoverTooltip(t('Share'), {key: 'topBarBtnTooltip'}),
testId('tb-share'),
);
}

@ -5,6 +5,7 @@
*
* It can be instantiated by calling showUserManagerModal with the UserAPI and IUserManagerOptions.
*/
import { makeT } from 'app/client/lib/localization';
import {commonUrls} from 'app/common/gristUrls';
import {capitalizeFirstWord, isLongerThan} from 'app/common/gutil';
import {FullUser} from 'app/common/LoginSessionAPI';
@ -42,6 +43,8 @@ import {menu, menuItem, menuText} from 'app/client/ui2018/menus';
import {confirmModal, cssAnimatedModal, cssModalBody, cssModalButtons, cssModalTitle,
IModalControl, modal} from 'app/client/ui2018/modals';
const t = makeT('UserManager');
export interface IUserManagerOptions {
permissionData: Promise<PermissionData>;
activeUser: FullUser|null;
@ -101,15 +104,15 @@ export function showUserManagerModal(userApi: UserAPI, options: IUserManagerOpti
}
};
if (model.isSelfRemoved.get()) {
const name = resourceName(model.resourceType);
const resourceType = resourceName(model.resourceType);
confirmModal(
`You are about to remove your own access to this ${name}`,
'Remove my access', tryToSaveChanges,
t(`You are about to remove your own access to this {{resourceType}}`, { resourceType }),
t('Remove my access'), tryToSaveChanges,
{
explanation: (
'Once you have removed your own access, ' +
'you will not be able to get it back without assistance ' +
`from someone else with sufficient access to the ${name}.`
t(`Once you have removed your own access, \
you will not be able to get it back without assistance \
from someone else with sufficient access to the {{resourceType}}.`, { resourceType })
),
}
);
@ -162,22 +165,22 @@ function buildUserManagerModal(
cssModalButtons(
{ style: 'margin: 32px 64px; display: flex;' },
(model.isPublicMember ? null :
bigPrimaryButton('Confirm',
bigPrimaryButton(t('Confirm'),
dom.boolAttr('disabled', (use) => !use(model.isAnythingChanged)),
dom.on('click', () => onConfirm(ctl)),
testId('um-confirm')
)
),
bigBasicButton(
model.isPublicMember ? 'Close' : 'Cancel',
model.isPublicMember ? t('Close') : t('Cancel'),
dom.on('click', () => ctl.close()),
testId('um-cancel')
),
(model.resourceType === 'document' && model.gristDoc && !model.isPersonal
? withInfoTooltip(
cssLink({href: urlState().makeUrl({docPage: 'acl'})},
dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''),
'Open Access Rules',
dom.text(use => use(model.isAnythingChanged) ? t('Save & ') : ''),
t('Open Access Rules'),
dom.on('click', (ev) => {
ev.preventDefault();
return onConfirm(ctl).then(() => urlState().pushUrl({docPage: 'acl'}));
@ -268,7 +271,7 @@ export class UserManager extends Disposable {
return dom('div',
cssOptionRowMultiple(
icon('AddUser'),
cssLabel('Invite multiple'),
cssLabel(t('Invite multiple')),
dom.on('click', (_ev) => buildMultiUserManagerModal(
this,
this._model,
@ -286,30 +289,31 @@ export class UserManager extends Disposable {
),
publicMember ? dom('span', { style: `float: right;` },
cssSmallPublicMemberIcon('PublicFilled'),
dom('span', 'Public access: '),
dom('span', t('Public access: ')),
cssOptionBtn(
menu(() => {
tooltipControl?.close();
return [
menuItem(() => publicMember.access.set(roles.VIEWER), 'On', testId(`um-public-option`)),
menuItem(() => publicMember.access.set(null), 'Off',
menuItem(() => publicMember.access.set(roles.VIEWER), t('On'), testId(`um-public-option`)),
menuItem(() => publicMember.access.set(null), t('Off'),
// Disable null access if anonymous access is inherited.
dom.cls('disabled', (use) => use(publicMember.inheritedAccess) !== null),
testId(`um-public-option`)
),
// If the 'Off' setting is disabled, show an explanation.
dom.maybe((use) => use(publicMember.inheritedAccess) !== null, () => menuText(
`Public access inherited from ${getResourceParent(this._model.resourceType)}. ` +
`To remove, set 'Inherit access' option to 'None'.`))
t(`Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.`,
{ parent: getResourceParent(this._model.resourceType) }
)))
];
}),
dom.text((use) => use(publicMember.effectiveAccess) ? 'On' : 'Off'),
dom.text((use) => use(publicMember.effectiveAccess) ? t('On') : t('Off')),
cssCollapseIcon('Collapse'),
testId('um-public-access')
),
hoverTooltip((ctl) => {
tooltipControl = ctl;
return 'Allow anyone with the link to open.';
return t('Allow anyone with the link to open.');
}),
) : null,
),
@ -373,19 +377,23 @@ export class UserManager extends Disposable {
const annotation = annotations.users.get(member.email);
if (!annotation) { return null; }
if (annotation.isSupport) {
return cssMemberType('Grist support');
return cssMemberType(t('Grist support'));
}
if (annotation.isMember && annotations.hasTeam) {
return cssMemberType('Team member');
return cssMemberType(t('Team member'));
}
const collaborator = annotations.hasTeam ? 'guest' : 'free collaborator';
const collaborator = annotations.hasTeam ? t('guest') : t('free collaborator');
const limit = annotation.collaboratorLimit;
if (!limit || !limit.top) { return null; }
const elements: HTMLSpanElement[] = [];
if (limit.at <= limit.top) {
elements.push(cssMemberType(`${limit.at} of ${limit.top} ${collaborator}s`));
elements.push(cssMemberType(
t(`{{limitAt}} of {{limitTop}} {{collaborator}}s`, { limitAt: limit.at, limitTop: limit.top, collaborator }))
);
} else {
elements.push(cssMemberTypeProblem(`${capitalizeFirstWord(collaborator)} limit exceeded`));
elements.push(cssMemberTypeProblem(
t(`{{collaborator}} limit exceeded`, { collaborator: capitalizeFirstWord(collaborator) }))
);
}
if (annotations.hasTeam) {
// Add a link for adding a member. For a doc, streamline this so user can make
@ -401,10 +409,10 @@ export class UserManager extends Disposable {
{ email: member.email }).catch(reportError);
}
}),
`Add ${member.name || 'member'} to your team`));
t(`Add {{member}} to your team`, { member: member.name || t('member') })));
} else if (limit.at >= limit.top) {
elements.push(cssLink({href: commonUrls.plans, target: '_blank'},
'Create a team to share with more people'));
t('Create a team to share with more people')));
}
return elements;
});
@ -418,13 +426,13 @@ export class UserManager extends Disposable {
let memberType: string;
if (annotation.isSupport) {
memberType = 'Grist support';
memberType = t('Grist support');
} else if (annotation.isMember && annotations.hasTeam) {
memberType = 'Team member';
memberType = t('Team member');
} else if (annotations.hasTeam) {
memberType = 'Outside collaborator';
memberType = t('Outside collaborator');
} else {
memberType = 'Collaborator';
memberType = t('Collaborator');
}
return cssMemberType(memberType, testId('um-member-annotation'));
@ -439,8 +447,8 @@ export class UserManager extends Disposable {
cssMemberListItem(
cssPublicMemberIcon('PublicFilled'),
cssMemberText(
cssMemberPrimary('Public Access'),
cssMemberSecondary('Anyone with link ', makeCopyBtn(this._options.linkToCopy)),
cssMemberPrimary(t('Public Access')),
cssMemberSecondary(t('Anyone with link '), makeCopyBtn(this._options.linkToCopy)),
),
this._memberRoleSelector(publicMember.effectiveAccess, publicMember.inheritedAccess, false,
this._model.publicUserSelectOptions
@ -472,12 +480,12 @@ export class UserManager extends Disposable {
cssMemberPrimary(name, testId('um-member-name')),
activeUser?.email ? cssMemberSecondary(activeUser.email) : null,
cssMemberPublicAccess(
dom('span', 'Public access', testId('um-member-annotation')),
dom('span', t('Public access'), testId('um-member-annotation')),
cssPublicAccessIcon('PublicFilled'),
),
),
cssRoleBtn(
accessLabel ?? 'Guest',
accessLabel ?? t('Guest'),
cssCollapseIcon('Collapse'),
dom.cls('disabled'),
testId('um-member-role'),
@ -522,23 +530,24 @@ export class UserManager extends Disposable {
)
),
// If the user's access is inherited, give an explanation on how to change it.
isActiveUser ? menuText(`User may not modify their own access.`) : null,
isActiveUser ? menuText(t(`User may not modify their own access.`)) : null,
// If the user's access is inherited, give an explanation on how to change it.
dom.maybe((use) => use(inherited) && !isActiveUser, () => menuText(
`User inherits permissions from ${getResourceParent(this._model.resourceType)}. To remove, ` +
`set 'Inherit access' option to 'None'.`)),
t(`User inherits permissions from {{parent}}. To remove, \
set 'Inherit access' option to 'None'.`, { parent: getResourceParent(this._model.resourceType) }))),
// If the user is a guest, give a description of the guest permission.
dom.maybe((use) => !this._model.isOrg && use(role) === roles.GUEST, () => menuText(
`User has view access to ${this._model.resourceType} resulting from manually-set access ` +
`to resources inside. If removed here, this user will lose access to resources inside.`)),
this._model.isOrg ? menuText(`No default access allows access to be ` +
`granted to individual documents or workspaces, rather than the full team site.`) : null
t(`User has view access to {{resource}} resulting from manually-set access \
to resources inside. If removed here, this user will lose access to resources inside.`,
{ resource: this._model.resourceType }))),
this._model.isOrg ? menuText(t(`No default access allows access to be \
granted to individual documents or workspaces, rather than the full team site.`)) : null
]),
dom.text((use) => {
// Get the label of the active role. Note that the 'Guest' role is assigned when the role
// is not found because it is not included as a selection.
const activeRole = allRoles.find((_role: IOrgMemberSelectOption) => use(role) === _role.value);
return activeRole ? activeRole.label : "Guest";
return activeRole ? activeRole.label : t("Guest");
}),
cssCollapseIcon('Collapse'),
this._model.isPersonal ? dom.cls('disabled') : null,
@ -634,7 +643,7 @@ function getFullUser(member: IEditableMember): FullUser {
// Create a "Copy Link" button.
function makeCopyBtn(linkToCopy: string|undefined, ...domArgs: DomElementArg[]) {
return linkToCopy && cssCopyBtn(cssCopyIcon('Copy'), 'Copy Link',
return linkToCopy && cssCopyBtn(cssCopyIcon('Copy'), t('Copy Link'),
dom.on('click', (ev, elem) => copyLink(elem, linkToCopy)),
testId('um-copy-link'),
...domArgs,
@ -646,7 +655,7 @@ function makeCopyBtn(linkToCopy: string|undefined, ...domArgs: DomElementArg[])
async function copyLink(elem: HTMLElement, link: string) {
await copyToClipboard(link);
setTestState({clipboard: link});
showTransientTooltip(elem, 'Link copied to clipboard', {key: 'copy-doc-link'});
showTransientTooltip(elem, t('Link copied to clipboard'), { key: 'copy-doc-link' });
}
async function manageTeam(appModel: AppModel,
@ -808,9 +817,9 @@ const cssMemberPublicAccess = styled(cssMemberSecondary, `
function renderTitle(resourceType: ResourceType, resource?: Resource, personal?: boolean) {
switch (resourceType) {
case 'organization': {
if (personal) { return 'Your role for this team site'; }
if (personal) { return t('Your role for this team site'); }
return [
'Manage members of team site',
t('Manage members of team site'),
!resource ? null : cssOrgName(
`${(resource as Organization).name} (`,
cssOrgDomain(`${(resource as Organization).domain}.getgrist.com`),
@ -819,12 +828,14 @@ function renderTitle(resourceType: ResourceType, resource?: Resource, personal?:
];
}
default: {
return personal ? `Your role for this ${resourceType}` : `Invite people to ${resourceType}`;
return personal ?
t(`Your role for this {{resourceType}}`, { resourceType }) :
t(`Invite people to {{resourceType}}`, { resourceType });
}
}
}
// Rename organization to team site.
function resourceName(resourceType: ResourceType): string {
return resourceType === 'organization' ? 'team site' : resourceType;
return resourceType === 'organization' ? t('team site') : resourceType;
}

@ -10,7 +10,7 @@ import { dom, styled } from "grainjs";
const t = makeT('WelcomeTour');
export const welcomeTour: IOnBoardingMsg[] = [
export const WelcomeTour: IOnBoardingMsg[] = [
{
title: t('Editing Data'),
body: () => [
@ -97,7 +97,7 @@ export const welcomeTour: IOnBoardingMsg[] = [
export function startWelcomeTour(onFinishCB: () => void) {
commands.allCommands.fieldTabOpen.run();
startOnBoarding(welcomeTour, onFinishCB);
startOnBoarding(WelcomeTour, onFinishCB);
}
const TopBarButtonIcon = styled(icon, `

@ -35,8 +35,8 @@ export function createForbiddenPage(appModel: AppModel, message?: string) {
return pagePanelsError(appModel, t("Access denied{{suffix}}", {suffix: ''}), [
dom.domComputed(appModel.currentValidUser, user => user ? [
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)})),
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)})),
] : [
// 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

@ -10,6 +10,9 @@ import { icon } from "app/client/ui2018/icons";
import { cssMenuItem, defaultMenuOptions, IOpenController, IPopupOptions, setPopupToFunc } from "popweasel";
import { mergeWith } from "lodash";
import { getOptionFull, SimpleList } from "../lib/simpleList";
import { makeT } from 'app/client/lib/localization';
const t = makeT('searchDropdown');
const testId = makeTestId('test-sd-');
@ -92,7 +95,7 @@ class DropdownWithSearch<T> extends Disposable {
cssMenuHeader(
cssSearchIcon('Search'),
this._inputElem = cssSearch(
{placeholder: this._options.placeholder || 'Search'},
{placeholder: this._options.placeholder || t('Search')},
dom.on('input', () => { this._update(); }),
dom.on('blur', () => setTimeout(() => this._inputElem.focus(), 0)),
),

@ -177,7 +177,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) {
cssTopBarBtn('Search',
testId('icon'),
dom.on('click', focusAndSelect),
hoverTooltip('Search', {key: 'topBarBtnTooltip'}),
hoverTooltip(t('Search'), {key: 'topBarBtnTooltip'}),
)
),
expandedSearch(

@ -39,7 +39,9 @@
"View As": "View As",
"Seed rules": "Seed rules",
"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.",
"Permission to edit document structure": "Permission to edit document structure"
"Permission to edit document structure": "Permission to edit document structure",
"This default should be changed if editors' access is to be limited. ": "This default should be changed if editors' access is to be limited. ",
"Allow editors to edit structure (e.g. modify and delete tables, columns, layouts), and to write formulas, which give access to all data regardless of read restrictions.": "Allow editors to edit structure (e.g. modify and delete tables, columns, layouts), and to write formulas, which give access to all data regardless of read restrictions."
},
"AccountPage": {
"API": "API",
@ -589,7 +591,8 @@
"Send to Google Drive": "Send to Google Drive",
"Show in folder": "Show in folder",
"Unsaved": "Unsaved",
"Work on a Copy": "Work on a Copy"
"Work on a Copy": "Work on a Copy",
"Share": "Share"
},
"SiteSwitcher": {
"Create new team site": "Create new team site",
@ -801,7 +804,8 @@
"Find Next ": "Find Next ",
"Find Previous ": "Find Previous ",
"No results": "No results",
"Search in document": "Search in document"
"Search in document": "Search in document",
"Search": "Search"
},
"sendToDrive": {
"Sending file to Google Drive": "Sending file to Google Drive"
@ -979,7 +983,9 @@
"Add New": "Add New",
"Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.": "Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.",
"Anchor Links": "Anchor Links",
"Custom Widgets": "Custom Widgets"
"Custom Widgets": "Custom Widgets",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.",
"You can choose one of our pre-made widgets or embed your own by providing its full URL.": "You can choose one of our pre-made widgets or embed your own by providing its full URL."
},
"DescriptionConfig": {
"DESCRIPTION": "DESCRIPTION"
@ -1041,6 +1047,61 @@
"You can always switch sites using the account menu.": "You can always switch sites using the account menu.",
"You have access to the following Grist sites.": "You have access to the following Grist sites."
},
"DescriptionTextArea": {
"DESCRIPTION": "DESCRIPTION"
},
"UserManager": {
"Add {{member}} to your team": "Add {{member}} to your team",
"Allow anyone with the link to open.": "Allow anyone with the link to open.",
"Anyone with link ": "Anyone with link ",
"Cancel": "Cancel",
"Close": "Close",
"Collaborator": "Collaborator",
"Confirm": "Confirm",
"Copy Link": "Copy Link",
"Create a team to share with more people": "Create a team to share with more people",
"Grist support": "Grist support",
"Guest": "Guest",
"Invite multiple": "Invite multiple",
"Invite people to {{resourceType}}": "Invite people to {{resourceType}}",
"Link copied to clipboard": "Link copied to clipboard",
"Manage members of team site": "Manage members of team site",
"No default access allows access to be granted to individual documents or workspaces, rather than the full team site.": "No default access allows access to be granted to individual documents or workspaces, rather than the full team site.",
"Off": "Off",
"On": "On",
"Once you have removed your own access, you will not be able to get it back without assistance from someone else with sufficient access to the {{name}}.": "Once you have removed your own access, you will not be able to get it back without assistance from someone else with sufficient access to the {{name}}.",
"Open Access Rules": "Open Access Rules",
"Outside collaborator": "Outside collaborator",
"Public Access": "Public Access",
"Public access": "Public access",
"Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.",
"Public access: ": "Public access: ",
"Remove my access": "Remove my access",
"Save & ": "Save & ",
"Team member": "Team member",
"User inherits permissions from {{parent})}. To remove, set 'Inherit access' option to 'None'.": "User inherits permissions from {{parent})}. To remove, set 'Inherit access' option to 'None'.",
"User may not modify their own access.": "User may not modify their own access.",
"Your role for this team site": "Your role for this team site",
"Your role for this {{resourceType}}": "Your role for this {{resourceType}}",
"free collaborator": "free collaborator",
"guest": "guest",
"member": "member",
"team site": "team site",
"{{collaborator}} limit exceeded": "{{collaborator}} limit exceeded",
"{{limitAt}} of {{limitTop}} {{collaborator}}s": "{{limitAt}} of {{limitTop}} {{collaborator}}s",
"No default access allows access to be granted to individual documents or workspaces, rather than the full team site.": "No default access allows access to be granted to individual documents or workspaces, rather than the full team site.",
"Once you have removed your own access, you will not be able to get it back without assistance from someone else with sufficient access to the {{resourceType}}.": "Once you have removed your own access, you will not be able to get it back without assistance from someone else with sufficient access to the {{resourceType}}.",
"User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.",
"User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.",
"You are about to remove your own access to this {{resourceType}}": "You are about to remove your own access to this {{resourceType}}"
},
"SearchModel": {
"Search all pages": "Search all pages",
"Search all tables": "Search all tables"
},
"searchDropdown": {
"Search": "Search"
},
"SupportGristNudge": {
"Close": "Close",
"Contribute": "Contribute",

Loading…
Cancel
Save