Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n: userManager translation + some forgotten translations #557

Merged
merged 4 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/client/aclui/AccessRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this is indeed missing from Weblate, and found some issues in i18next-scanner for this (i18next/i18next-scanner#35 and i18next/i18next-scanner#227). Thank you for finding and fixing!

Should the whitespace at the start of the continuation lines be removed? Otherwise the extra whitespace becomes part of the string.

BTW, it looks like there are a few other such instances of concatenated strings; I found some more using this command grep -rI '\bt(.*+$' ./app.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, it's a good point

availableBits: ['schemaEdit'],
...schemaEditRules.denyEditors,
},
Expand Down Expand Up @@ -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'),
Expand Down
6 changes: 5 additions & 1 deletion app/client/models/SearchModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ 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';

// Use 'tt' instade of 't', because 't' is already delared in the upper scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use t here for consistency with all other code. The lint error about t being already declared is in unrelated lines which seem better candidates to rename the variable (e.g. one maps tables, so could replace t with table; and the other maps pages, so could replace t with page).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh your right, thanks ! I hadn't quite read linter's error, I changed it

const tt = makeT('SearchModel');

/**
* SearchModel used to maintain the state of the search UI.
Expand Down Expand Up @@ -468,7 +472,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');
tt('Search all tables') : tt('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.
Expand Down
6 changes: 3 additions & 3 deletions app/client/ui/ShareMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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'),
),
);
Expand All @@ -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'),
);
}
Expand Down
103 changes: 57 additions & 46 deletions app/client/ui/UserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -103,13 +106,13 @@ export function showUserManagerModal(userApi: UserAPI, options: IUserManagerOpti
if (model.isSelfRemoved.get()) {
const name = 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 {{name}}`, { name }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming this from "name" to "resourceType" would be clearer I think. It may have values like "document", "workspace", or "team site".

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 \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, it may be better to remove the leading whitespace on continuation lines, even if code ends up less well-aligned.

from someone else with sufficient access to the {{name}}.`, { name })
),
}
);
Expand Down Expand Up @@ -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'}));
Expand Down Expand Up @@ -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,
Expand All @@ -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,
),
Expand Down Expand Up @@ -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
Expand All @@ -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;
});
Expand All @@ -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'));
Expand All @@ -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
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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, \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an extra parenthesis crept in inside of })}.

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 {{ressource}} resulting from manually-set access \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The English version of "resource" has one "s".

to resources inside. If removed here, this user will lose access to resources inside.`,
{ ressource: 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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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`),
Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions app/client/ui/WelcomeTour.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => [
Expand Down Expand Up @@ -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, `
Expand Down
5 changes: 4 additions & 1 deletion app/client/ui/searchDropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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-');

Expand Down Expand Up @@ -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)),
),
Expand Down
2 changes: 1 addition & 1 deletion app/client/ui2018/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading