mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	i18n: userManager translation + some forgotten translations (#557)
* translation: add userManager translation + some forgotten translations * use '\' caracter for multiple-line strings
This commit is contained in:
		
							parent
							
								
									b4b0c805ff
								
							
						
					
					
						commit
						61bd064f73
					
				| @ -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…
	
		Reference in New Issue
	
	Block a user