(core) User language switcher

Summary:
New language selector on the Account page for logged-in users.
New icon for switching language for an anonymous user.

For anonymous users, language is stored in a cookie grist_user_locale.
Language is stored in user settings for authenticated users and takes
precedence over what is stored in the cookie.

Test Plan: New tests

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3766
This commit is contained in:
Jarosław Sadziński
2023-01-24 14:13:18 +01:00
parent abea735470
commit 90d3ee037a
36 changed files with 696 additions and 74 deletions

View File

@@ -1,6 +1,8 @@
import * as crypto from 'crypto';
import * as express from 'express';
import {EntityManager} from 'typeorm';
import * as cookie from 'cookie';
import {Request} from 'express';
import {ApiError} from 'app/common/ApiError';
import {FullUser} from 'app/common/LoginSessionAPI';
@@ -13,10 +15,10 @@ import log from 'app/server/lib/log';
import {addPermit, clearSessionCacheIfNeeded, getDocScope, getScope, integerParam,
isParameterOn, sendOkReply, sendReply, stringParam} from 'app/server/lib/requestUtils';
import {IWidgetRepository} from 'app/server/lib/WidgetRepository';
import {Request} from 'express';
import {User} from './entity/User';
import {HomeDBManager, QueryResult, Scope} from './lib/HomeDBManager';
import {getCookieDomain} from 'app/server/lib/gristSessions';
// Special public organization that contains examples and templates.
export const TEMPLATES_ORG_DOMAIN = process.env.GRIST_ID_PREFIX ?
@@ -371,6 +373,23 @@ export class ApiServer {
res.sendStatus(200);
}));
// POST /api/profile/user/locale
// Body params: string
// Update users profile.
this._app.post('/api/profile/user/locale', expressWrap(async (req, res) => {
const userId = getAuthorizedUserId(req);
await this._dbManager.updateUserOptions(userId, {locale: req.body.locale || null});
res.append('Set-Cookie', cookie.serialize('grist_user_locale', req.body.locale || '', {
httpOnly: false, // make available to client-side scripts
domain: getCookieDomain(req),
path: '/',
secure: true,
maxAge: req.body.locale ? 31536000 : 0,
sameSite: 'None', // there is no security concern to expose this information.
}));
res.sendStatus(200);
}));
// POST /api/profile/allowGoogleLogin
// Update user's preference for allowing Google login.
this._app.post('/api/profile/allowGoogleLogin', expressWrap(async (req, res) => {

View File

@@ -490,6 +490,7 @@ export class HomeDBManager extends EventEmitter {
name: user.name,
picture: user.picture,
ref: user.ref,
locale: user.options?.locale
};
if (this.getAnonymousUserId() === user.id) {
result.anonymous = true;
@@ -2663,7 +2664,8 @@ export class HomeDBManager extends EventEmitter {
email: login.displayEmail,
name: login.user.name,
picture: login.user.picture,
anonymous: login.user.id === this.getAnonymousUserId()
anonymous: login.user.id === this.getAnonymousUserId(),
locale: login.user.options?.locale
};
}
return profiles.map(profile => completedProfiles[normalizeEmail(profile.email)])