let user choose interface language #51

This commit is contained in:
rimu 2024-05-09 13:59:52 +12:00
parent 3bc30ec99c
commit 29c2a05d38
8 changed files with 62 additions and 7 deletions

View file

@ -4,7 +4,7 @@
import logging import logging
from logging.handlers import SMTPHandler, RotatingFileHandler from logging.handlers import SMTPHandler, RotatingFileHandler
import os import os
from flask import Flask, request, current_app from flask import Flask, request, current_app, session
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager from flask_login import LoginManager
@ -20,10 +20,13 @@ from config import Config
def get_locale(): def get_locale():
try: if session.get('ui_language', None):
return request.accept_languages.best_match(current_app.config['LANGUAGES']) return session['ui_language']
except: else:
return 'en' 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} db = SQLAlchemy() # engine_options={'pool_size': 5, 'max_overflow': 10} # session_options={"autoflush": False}

View file

@ -56,6 +56,7 @@ def login():
if user.waiting_for_approval(): if user.waiting_for_approval():
return redirect(url_for('auth.please_wait')) return redirect(url_for('auth.please_wait'))
login_user(user, remember=True) login_user(user, remember=True)
session['ui_language'] = user.interface_language
current_user.last_seen = utcnow() current_user.last_seen = utcnow()
current_user.ip_address = ip_address() current_user.ip_address = ip_address()
db.session.commit() db.session.commit()

View file

@ -606,6 +606,8 @@ class User(UserMixin, db.Model):
theme = db.Column(db.String(20), default='') theme = db.Column(db.String(20), default='')
referrer = db.Column(db.String(256)) referrer = db.Column(db.String(256))
markdown_editor = db.Column(db.Boolean, default=False) 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") 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") cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan")

View file

@ -31,6 +31,7 @@
{{ render_field(form.searchable) }} {{ render_field(form.searchable) }}
{{ render_field(form.indexable) }} {{ render_field(form.indexable) }}
<h5> Preferences </h5> <h5> Preferences </h5>
{{ render_field(form.interface_language) }}
{{ render_field(form.markdown_editor) }} {{ render_field(form.markdown_editor) }}
{{ render_field(form.default_sort) }} {{ render_field(form.default_sort) }}
{{ render_field(form.theme) }} {{ render_field(form.theme) }}

View file

@ -3,6 +3,7 @@ from flask_login import current_user
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \ from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \
RadioField, DateField, SelectField RadioField, DateField, SelectField
from wtforms.fields.choices import SelectMultipleField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
from flask_babel import _, lazy_gettext as _l from flask_babel import _, lazy_gettext as _l
@ -31,6 +32,7 @@ class ProfileForm(FlaskForm):
class SettingsForm(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')) newsletter = BooleanField(_l('Subscribe to email newsletter'))
email_unread = BooleanField(_l('Receive email about missed notifications')) email_unread = BooleanField(_l('Receive email about missed notifications'))
ignore_bots = BooleanField(_l('Hide posts by bots')) ignore_bots = BooleanField(_l('Hide posts by bots'))

View file

@ -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 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_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 import db, cache, celery
from app.activitypub.signature import post_request, default_context from app.activitypub.signature import post_request, default_context
@ -164,6 +164,13 @@ def change_settings():
abort(404) abort(404)
form = SettingsForm() form = SettingsForm()
form.theme.choices = theme_list() 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(): if form.validate_on_submit():
propagate_indexable = form.indexable.data != current_user.indexable propagate_indexable = form.indexable.data != current_user.indexable
current_user.newsletter = form.newsletter.data current_user.newsletter = form.newsletter.data
@ -176,6 +183,8 @@ def change_settings():
current_user.theme = form.theme.data current_user.theme = form.theme.data
current_user.email_unread = form.email_unread.data current_user.email_unread = form.email_unread.data
current_user.markdown_editor = form.markdown_editor.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'] import_file = request.files['import_file']
if propagate_indexable: if propagate_indexable:
db.session.execute(text('UPDATE "post" set indexable = :indexable WHERE user_id = :user_id'), 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.default_sort.data = current_user.default_sort
form.theme.data = current_user.theme form.theme.data = current_user.theme
form.markdown_editor.data = current_user.markdown_editor 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, return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user,
moderating_communities=moderating_communities(current_user.get_id()), moderating_communities=moderating_communities(current_user.get_id()),

View file

@ -22,7 +22,7 @@ class Config(object):
RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY") or None RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY") or None
RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY") or None RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY") or None
MODE = os.environ.get('MODE') or 'development' 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))) FULL_AP_CONTEXT = bool(int(os.environ.get('FULL_AP_CONTEXT', 0)))
CACHE_TYPE = os.environ.get('CACHE_TYPE') or 'FileSystemCache' CACHE_TYPE = os.environ.get('CACHE_TYPE') or 'FileSystemCache'
CACHE_REDIS_URL = os.environ.get('CACHE_REDIS_URL') or 'redis://localhost:6379/1' CACHE_REDIS_URL = os.environ.get('CACHE_REDIS_URL') or 'redis://localhost:6379/1'

View file

@ -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 ###