From 29c2a05d38670d22ce73838a59180d28a18e730c Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Thu, 9 May 2024 13:59:52 +1200 Subject: [PATCH] let user choose interface language #51 --- app/__init__.py | 13 ++++--- app/auth/routes.py | 1 + app/models.py | 2 ++ app/templates/user/edit_settings.html | 1 + app/user/forms.py | 2 ++ app/user/routes.py | 12 ++++++- config.py | 2 +- .../versions/e73996747d7e_user_language.py | 36 +++++++++++++++++++ 8 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 migrations/versions/e73996747d7e_user_language.py diff --git a/app/__init__.py b/app/__init__.py index 6ba63856..3aaf86ec 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,7 +4,7 @@ import logging from logging.handlers import SMTPHandler, RotatingFileHandler import os -from flask import Flask, request, current_app +from flask import Flask, request, current_app, session from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager @@ -20,10 +20,13 @@ from config import Config def get_locale(): - try: - return request.accept_languages.best_match(current_app.config['LANGUAGES']) - except: - return 'en' + if session.get('ui_language', None): + return session['ui_language'] + else: + try: + return request.accept_languages.best_match(current_app.config['LANGUAGES']) + except: + return 'en' db = SQLAlchemy() # engine_options={'pool_size': 5, 'max_overflow': 10} # session_options={"autoflush": False} diff --git a/app/auth/routes.py b/app/auth/routes.py index c09a9be7..55d0b464 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -56,6 +56,7 @@ def login(): if user.waiting_for_approval(): return redirect(url_for('auth.please_wait')) login_user(user, remember=True) + session['ui_language'] = user.interface_language current_user.last_seen = utcnow() current_user.ip_address = ip_address() db.session.commit() diff --git a/app/models.py b/app/models.py index 1766682b..d1ac093c 100644 --- a/app/models.py +++ b/app/models.py @@ -606,6 +606,8 @@ class User(UserMixin, db.Model): theme = db.Column(db.String(20), default='') referrer = db.Column(db.String(256)) markdown_editor = db.Column(db.Boolean, default=False) + interface_language = db.Column(db.String(10)) # a locale that the translation system understands e.g. 'en' or 'en-us'. If empty, use browser default + language_id = db.Column(db.Integer, db.ForeignKey('language.id')) # the default choice in the language dropdown when composing posts & comments avatar = db.relationship('File', lazy='joined', foreign_keys=[avatar_id], single_parent=True, cascade="all, delete-orphan") cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan") diff --git a/app/templates/user/edit_settings.html b/app/templates/user/edit_settings.html index feb8a9d4..06422d9e 100644 --- a/app/templates/user/edit_settings.html +++ b/app/templates/user/edit_settings.html @@ -31,6 +31,7 @@ {{ render_field(form.searchable) }} {{ render_field(form.indexable) }}
Preferences
+ {{ render_field(form.interface_language) }} {{ render_field(form.markdown_editor) }} {{ render_field(form.default_sort) }} {{ render_field(form.theme) }} diff --git a/app/user/forms.py b/app/user/forms.py index a58a7c1e..03669cdd 100644 --- a/app/user/forms.py +++ b/app/user/forms.py @@ -3,6 +3,7 @@ from flask_login import current_user from flask_wtf import FlaskForm from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \ RadioField, DateField, SelectField +from wtforms.fields.choices import SelectMultipleField from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional from flask_babel import _, lazy_gettext as _l @@ -31,6 +32,7 @@ class ProfileForm(FlaskForm): class SettingsForm(FlaskForm): + interface_language = SelectField(_l('Interface language'), coerce=str, validators=[Optional()], render_kw={'class': 'form-select'}) newsletter = BooleanField(_l('Subscribe to email newsletter')) email_unread = BooleanField(_l('Receive email about missed notifications')) ignore_bots = BooleanField(_l('Hide posts by bots')) diff --git a/app/user/routes.py b/app/user/routes.py index 5bfe7a05..3ab81f85 100644 --- a/app/user/routes.py +++ b/app/user/routes.py @@ -3,7 +3,7 @@ from time import sleep from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, json from flask_login import login_user, logout_user, current_user, login_required -from flask_babel import _ +from flask_babel import _, lazy_gettext as _l from app import db, cache, celery from app.activitypub.signature import post_request, default_context @@ -164,6 +164,13 @@ def change_settings(): abort(404) form = SettingsForm() form.theme.choices = theme_list() + form.interface_language.choices = [ + ('', _l('Auto-detect')), + ('ca', _l('Catalan')), + ('en', _l('English')), + ('fr', _l('French')), + ('de', _l('German')), + ] if form.validate_on_submit(): propagate_indexable = form.indexable.data != current_user.indexable current_user.newsletter = form.newsletter.data @@ -176,6 +183,8 @@ def change_settings(): current_user.theme = form.theme.data current_user.email_unread = form.email_unread.data current_user.markdown_editor = form.markdown_editor.data + current_user.interface_language = form.interface_language.data + session['ui_language'] = form.interface_language.data import_file = request.files['import_file'] if propagate_indexable: db.session.execute(text('UPDATE "post" set indexable = :indexable WHERE user_id = :user_id'), @@ -213,6 +222,7 @@ def change_settings(): form.default_sort.data = current_user.default_sort form.theme.data = current_user.theme form.markdown_editor.data = current_user.markdown_editor + form.interface_language.data = current_user.interface_language return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user, moderating_communities=moderating_communities(current_user.get_id()), diff --git a/config.py b/config.py index 49b9d109..a8e03ab1 100644 --- a/config.py +++ b/config.py @@ -22,7 +22,7 @@ class Config(object): RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY") or None RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY") or None MODE = os.environ.get('MODE') or 'development' - LANGUAGES = ['de', 'en'] + LANGUAGES = ['de', 'en', 'fr'] FULL_AP_CONTEXT = bool(int(os.environ.get('FULL_AP_CONTEXT', 0))) CACHE_TYPE = os.environ.get('CACHE_TYPE') or 'FileSystemCache' CACHE_REDIS_URL = os.environ.get('CACHE_REDIS_URL') or 'redis://localhost:6379/1' diff --git a/migrations/versions/e73996747d7e_user_language.py b/migrations/versions/e73996747d7e_user_language.py new file mode 100644 index 00000000..56694440 --- /dev/null +++ b/migrations/versions/e73996747d7e_user_language.py @@ -0,0 +1,36 @@ +"""user language + +Revision ID: e73996747d7e +Revises: 94828ddc7c63 +Create Date: 2024-05-09 13:37:17.010724 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e73996747d7e' +down_revision = '94828ddc7c63' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('interface_language', sa.String(length=10), nullable=True)) + batch_op.add_column(sa.Column('language_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key(None, 'language', ['language_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_column('language_id') + batch_op.drop_column('interface_language') + + # ### end Alembic commands ###