basic i18n support

This commit is contained in:
Your Name 2024-04-03 23:10:21 -04:00 committed by Henri Dickson
parent 3e301ad453
commit fca9b6155d
26 changed files with 2709 additions and 90 deletions

4
.gitignore vendored
View file

@ -42,3 +42,7 @@ log
# test coverage
/.coverage
# translations
*.mo

View file

@ -36,13 +36,14 @@ RUN --mount=type=cache,sharing=locked,target=/var/cache/apt-run apt-get update \
RUN busybox --install
# postgresql and redis cli are not required, but install for development convenience
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt-run apt-get install -y --no-install-recommends postgresql-client redis-tools
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt-run apt-get install -y --no-install-recommends postgresql-client redis-tools gettext
RUN useradd -U app
RUN rm -rf /var/lib/apt/lists/*
COPY --from=build /neodb /neodb
WORKDIR /neodb
COPY --from=build /neodb-venv /neodb-venv
RUN /neodb-venv/bin/django-admin compilemessages
RUN NEODB_SECRET_KEY="t" NEODB_SITE_DOMAIN="x.y" NEODB_SITE_NAME="z" /neodb-venv/bin/python3 manage.py compilescss
RUN NEODB_SECRET_KEY="t" NEODB_SITE_DOMAIN="x.y" NEODB_SITE_NAME="z" /neodb-venv/bin/python3 manage.py collectstatic --noinput

View file

@ -41,6 +41,8 @@ env = environ.FileAwareEnv(
),
# Links in site footer
NEODB_SITE_LINKS=(dict, {}),
# Default language
NEODB_LANGUAGE=(str, "zh-hans"),
# Invite only mode
# when True: user will not be able to register unless with invite token
# (generated by `neodb-manage invite --create`)
@ -323,7 +325,8 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"hijack.middleware.HijackUserMiddleware",
"django.middleware.locale.LocaleMiddleware",
# "django.middleware.locale.LocaleMiddleware",
"users.middlewares.LanguageMiddleware",
"tz_detect.middleware.TimezoneMiddleware",
"auditlog.middleware.AuditlogMiddleware",
# "maintenance_mode.middleware.MaintenanceModeMiddleware", # this should be last if enabled
@ -384,15 +387,12 @@ if SLACK_TOKEN:
MARKDOWNX_MARKDOWNIFY_FUNCTION = "journal.models.render_md"
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = "zh-hans"
LANGUAGE_CODE = env("NEODB_LANGUAGE", default="zh-hans") # type: ignore
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
# LANGUAGES = (
# ("en", _("English")),
# ("zh-hans", _("Simplified Chinese")),
# )
LANGUAGES = (
# ("en", _("English")),
("zh-hans", _("Simplified Chinese")),
)
TIME_ZONE = env("NEODB_TIMEZONE", default="Asia/Shanghai") # type: ignore

View file

@ -118,7 +118,7 @@ class ItemType(models.TextChoices):
PodcastEpisode = "podcastepisode", _("Podcast Episode")
Performance = "performance", _("Performance")
PerformanceProduction = "production", _("Production")
FanFic = "fanfic", _("Fanfix")
FanFic = "fanfic", _("Fanfic")
Exhibition = "exhibition", _("Exhibition")
Collection = "collection", _("Collection")

View file

@ -2,6 +2,7 @@ from datetime import date
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy
from catalog.common import (
BaseSchema,
@ -66,7 +67,7 @@ class Album(Item):
default=list,
)
genre = jsondata.ArrayField(
verbose_name=_("genre"),
verbose_name=pgettext_lazy("music", "genre"),
base_field=models.CharField(blank=True, default="", max_length=50),
null=True,
blank=True,

View file

@ -54,7 +54,7 @@
{% if request.user.is_authenticated and not mark.shelf_type %}
<div id="item-primary-action" class="right mark">
<div class="item-action item-mark-buttons">
{% for k, v in shelf_labels %}
{% for k, v in shelf_actions %}
{% if v and k != 'dropped' %}
<button class="primary"
data-status="{{ k }}"

View file

@ -17,9 +17,9 @@ from journal.models import (
Comment,
Mark,
Review,
ShelfManager,
ShelfMember,
ShelfType,
get_shelf_labels_for_category,
q_piece_in_home_feed_of_user,
q_piece_visible_to_user,
)
@ -94,7 +94,7 @@ def retrieve(request, item_path, item_uuid):
my_collections = []
collection_list = []
child_item_comments = []
shelf_labels = get_shelf_labels_for_category(item.category)
shelf_actions = ShelfManager.get_actions_for_category(item.category)
if request.user.is_authenticated:
visible = q_piece_visible_to_user(request.user)
mark = Mark(request.user.identity, item)
@ -128,7 +128,7 @@ def retrieve(request, item_path, item_uuid):
"child_item_comments": child_item_comments,
"my_collections": my_collections,
"collection_list": collection_list,
"shelf_labels": shelf_labels,
"shelf_actions": shelf_actions,
},
)

View file

@ -1,4 +1,5 @@
from django import template
from django.conf import settings
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
@ -18,6 +19,11 @@ def all_categories():
return item_categories()
@register.simple_tag
def all_languages():
return settings.LANGUAGES
@register.filter(is_safe=True)
@stringfilter
def duration_format(value, unit):

View file

@ -17,15 +17,7 @@ from .mark import Mark
from .rating import Rating
from .renderers import render_md
from .review import Review
from .shelf import (
SHELF_LABELS,
Shelf,
ShelfLogEntry,
ShelfManager,
ShelfMember,
ShelfType,
get_shelf_labels_for_category,
)
from .shelf import Shelf, ShelfLogEntry, ShelfManager, ShelfMember, ShelfType
from .tag import Tag, TagManager, TagMember
from .utils import (
journal_exists_for_item,

View file

@ -71,6 +71,21 @@ class Mark:
def action_label_for_feed(self) -> str:
return str(self.action_label)
def get_action_for_feed(self, item_link=None):
if self.shelfmember and self.shelf_type:
tpl = ShelfManager.get_action_template(self.shelf_type, self.item.category)
elif self.comment:
tpl = ShelfManager.get_action_template(
ShelfType.PROGRESS, self.comment.item.category
)
else:
tpl = ""
if item_link:
i = f'<a href="{item_link}">{self.item.display_title}</a>'
else:
i = self.item.display_title
return _(tpl).format(item=i)
@property
def shelf_label(self) -> str | None:
return (

View file

@ -4,7 +4,6 @@ from functools import cached_property
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from markdownify import markdownify as md
from markdownx.models import MarkdownxField

View file

@ -24,44 +24,253 @@ class ShelfType(models.TextChoices):
DROPPED = ("dropped", _("DROPPED"))
SHELF_LABELS = [
[ItemCategory.Book, ShelfType.WISHLIST, _("wants to read")],
[ItemCategory.Book, ShelfType.PROGRESS, _("started reading")],
[ItemCategory.Book, ShelfType.COMPLETE, _("finished reading")],
[ItemCategory.Book, ShelfType.DROPPED, _("stopped reading")],
[ItemCategory.Movie, ShelfType.WISHLIST, _("wants to watch")],
[ItemCategory.Movie, ShelfType.PROGRESS, _("started watching")],
[ItemCategory.Movie, ShelfType.COMPLETE, _("finished watching")],
[ItemCategory.Movie, ShelfType.DROPPED, _("stopped watching")],
[ItemCategory.TV, ShelfType.WISHLIST, _("wants to watch")],
[ItemCategory.TV, ShelfType.PROGRESS, _("started watching")],
[ItemCategory.TV, ShelfType.COMPLETE, _("finished watching")],
[ItemCategory.TV, ShelfType.DROPPED, _("stopped watching")],
[ItemCategory.Music, ShelfType.WISHLIST, _("wants to listen")],
[ItemCategory.Music, ShelfType.PROGRESS, _("started listening")],
[ItemCategory.Music, ShelfType.COMPLETE, _("finished listening")],
[ItemCategory.Music, ShelfType.DROPPED, _("stopped listening")],
[ItemCategory.Game, ShelfType.WISHLIST, _("wants to play")],
[ItemCategory.Game, ShelfType.PROGRESS, _("started playing")],
[ItemCategory.Game, ShelfType.COMPLETE, _("finished playing")],
[ItemCategory.Game, ShelfType.DROPPED, _("stopped playing")],
[ItemCategory.Podcast, ShelfType.WISHLIST, _("wants to listen")],
[ItemCategory.Podcast, ShelfType.PROGRESS, _("started listening")],
[ItemCategory.Podcast, ShelfType.COMPLETE, _("finished listening")],
[ItemCategory.Podcast, ShelfType.DROPPED, _("stopped listening")],
[ItemCategory.Performance, ShelfType.WISHLIST, _("wants to see")],
_REVIEWED = "reviewed"
_SHELF_LABELS = [
[
ItemCategory.Book,
ShelfType.WISHLIST,
_("books to read"),
_("want to read"),
_("wants to read {item}"),
],
[
ItemCategory.Book,
ShelfType.PROGRESS,
_("books reading"),
_("start reading"),
_("started reading {item}"),
],
[
ItemCategory.Book,
ShelfType.COMPLETE,
_("books completed"),
_("finish reading"),
_("finished reading {item}"),
],
[
ItemCategory.Book,
ShelfType.DROPPED,
_("books dropped"),
_("stop reading"),
_("stopped reading {item}"),
],
[
ItemCategory.Book,
_REVIEWED,
_("books reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.Movie,
ShelfType.WISHLIST,
_("movies to watch"),
_("want to watch"),
_("wants to watch {item}"),
],
[
ItemCategory.Movie,
ShelfType.PROGRESS,
_("movies watching"),
_("start watching"),
_("started watching {item}"),
],
[
ItemCategory.Movie,
ShelfType.COMPLETE,
_("movies watched"),
_("finish watching"),
_("finished watching {item}"),
],
[
ItemCategory.Movie,
ShelfType.DROPPED,
_("movies dropped"),
_("stop watching"),
_("stopped watching {item}"),
],
[
ItemCategory.Movie,
_REVIEWED,
_("movies reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.TV,
ShelfType.WISHLIST,
_("TV shows to watch"),
_("want to watch"),
_("wants to watch {item}"),
],
[
ItemCategory.TV,
ShelfType.PROGRESS,
_("TV shows watching"),
_("start watching"),
_("started watching {item}"),
],
[
ItemCategory.TV,
ShelfType.COMPLETE,
_("TV shows watched"),
_("finish watching"),
_("finished watching {item}"),
],
[
ItemCategory.TV,
ShelfType.DROPPED,
_("TV shows dropped"),
_("stop watching"),
_("stopped watching {item}"),
],
[
ItemCategory.TV,
_REVIEWED,
_("TV shows reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.Music,
ShelfType.WISHLIST,
_("albums to listen"),
_("want to listen"),
_("wants to listen {item}"),
],
[
ItemCategory.Music,
ShelfType.PROGRESS,
_("albums listening"),
_("start listening"),
_("started listening {item}"),
],
[
ItemCategory.Music,
ShelfType.COMPLETE,
_("albums to listen"),
_("finish listening"),
_("finished listening {item}"),
],
[
ItemCategory.Music,
ShelfType.DROPPED,
_("albums dropped"),
_("stop listening"),
_("stopped listening {item}"),
],
[
ItemCategory.Music,
_REVIEWED,
_("albums reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.Game,
ShelfType.WISHLIST,
_("games to play"),
_("want to play"),
_("wants to play {item}"),
],
[
ItemCategory.Game,
ShelfType.PROGRESS,
_("games playing"),
_("start playing"),
_("started playing {item}"),
],
[
ItemCategory.Game,
ShelfType.COMPLETE,
_("games played"),
_("finish playing"),
_("finished playing {item}"),
],
[
ItemCategory.Game,
ShelfType.DROPPED,
_("games dropped"),
_("stop playing"),
_("stopped playing {item}"),
],
[
ItemCategory.Game,
_REVIEWED,
_("games reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.Podcast,
ShelfType.WISHLIST,
_("podcasts to listen"),
_("want to listen"),
_("wants to listen {item}"),
],
[
ItemCategory.Podcast,
ShelfType.PROGRESS,
_("podcasts listening"),
_("start listening"),
_("started listening {item}"),
],
[
ItemCategory.Podcast,
ShelfType.COMPLETE,
_("podcasts listened"),
_("finish listening"),
_("finished listening {item}"),
],
[
ItemCategory.Podcast,
ShelfType.DROPPED,
_("podcasts dropped"),
_("stop listening"),
_("stopped listening {item}"),
],
[
ItemCategory.Podcast,
_REVIEWED,
_("podcasts reviewed"),
_("review"),
_("wrote a review of {item}"),
],
[
ItemCategory.Performance,
ShelfType.WISHLIST,
_("performances to see"),
_("want to see"),
_("wants to see {item}"),
],
# disable progress shelf for Performance
[ItemCategory.Performance, ShelfType.PROGRESS, ""],
[ItemCategory.Performance, ShelfType.COMPLETE, _("finished seeing")],
[ItemCategory.Performance, ShelfType.DROPPED, _("stopped seeing")],
[ItemCategory.Performance, ShelfType.PROGRESS, "", "", ""],
[
ItemCategory.Performance,
ShelfType.COMPLETE,
_("performances saw"),
_("finish seeing"),
_("finished seeing {item}"),
],
[
ItemCategory.Performance,
ShelfType.DROPPED,
_("performances dropped"),
_("stop seeing"),
_("stopped seeing {item}"),
],
[
ItemCategory.Performance,
_REVIEWED,
_("performances reviewed"),
_("review"),
_("wrote a review of {item}"),
],
]
# grammatically problematic, for translation only
def get_shelf_labels_for_category(item_category: ItemCategory):
return [(n[1], n[2]) for n in SHELF_LABELS if n[0] == item_category]
class ShelfMember(ListMember):
parent = models.ForeignKey(
"Shelf", related_name="members", on_delete=models.CASCADE
@ -284,23 +493,39 @@ class ShelfManager:
# )
# return shelf.members.all().order_by
@classmethod
def get_labels_for_category(cls, item_category: ItemCategory):
return [(n[1], n[2]) for n in _SHELF_LABELS if n[0] == item_category]
@classmethod
def get_actions_for_category(cls, item_category: ItemCategory):
return [
(n[1], n[3])
for n in _SHELF_LABELS
if n[0] == item_category and n[1] != _REVIEWED
]
@classmethod
def get_label(cls, shelf_type: ShelfType | str, item_category: ItemCategory) -> str:
st = str(shelf_type)
sts = [n[2] for n in _SHELF_LABELS if n[0] == item_category and n[1] == st]
return sts[0] if sts else st
@classmethod
def get_action_label(
cls, shelf_type: ShelfType | str, item_category: ItemCategory
) -> str:
st = str(shelf_type)
sts = [n[2] for n in SHELF_LABELS if n[0] == item_category and n[1] == st]
sts = [n[3] for n in _SHELF_LABELS if n[0] == item_category and n[1] == st]
return sts[0] if sts else st
@classmethod
def get_label(cls, shelf_type: ShelfType, item_category: ItemCategory):
ic = ItemCategory(item_category).label
st = cls.get_action_label(shelf_type, item_category)
return (
_("{shelf_label} {item_category}").format(shelf_label=st, item_category=ic)
if st
else None
)
def get_action_template(
cls, shelf_type: ShelfType | str, item_category: ItemCategory
) -> str:
st = str(shelf_type)
sts = [n[4] for n in _SHELF_LABELS if n[0] == item_category and n[1] == st]
return sts[0] if sts else st
@staticmethod
def get_manager_for_user(owner: APIdentity):

View file

@ -23,11 +23,11 @@
<div>
<form method="post" action="{% url 'journal:mark' item.uuid %}">
{% csrf_token %}
{% if shelf_labels %}
{% if shelf_actions %}
<div class="grid mark-line">
<div>
<fieldset>
{% for k, v in shelf_labels %}
{% for k, v in shelf_actions %}
{% if v %}
<input type="radio"
name="status"

View file

@ -102,7 +102,9 @@ def render_list(
page_number = int(request.GET.get("page", default=1))
members = paginator.get_page(page_number)
pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
shelf_labels = get_shelf_labels_for_category(item_category) if item_category else []
shelf_labels = (
ShelfManager.get_labels_for_category(item_category) if item_category else []
)
return render(
request,
f"user_{type}_list.html",

View file

@ -15,7 +15,7 @@ from common.utils import AuthedHttpRequest, get_uuid_or_404
from mastodon.api import boost_toot_later, share_comment
from takahe.utils import Takahe
from ..models import Comment, Mark, ShelfType, TagManager, get_shelf_labels_for_category
from ..models import Comment, Mark, ShelfManager, ShelfType, TagManager
from .common import render_list, render_relogin, target_identity_required
_logger = logging.getLogger(__name__)
@ -45,7 +45,7 @@ def mark(request: AuthedHttpRequest, item_uuid):
mark = Mark(request.user.identity, item)
if request.method == "GET":
tags = request.user.identity.tag_manager.get_item_tags(item)
shelf_labels = get_shelf_labels_for_category(item.category)
shelf_actions = ShelfManager.get_actions_for_category(item.category)
shelf_type = request.GET.get("shelf_type", mark.shelf_type)
return render(
request,
@ -55,7 +55,7 @@ def mark(request: AuthedHttpRequest, item_uuid):
"mark": mark,
"shelf_type": shelf_type,
"tags": ",".join(tags),
"shelf_labels": shelf_labels,
"shelf_actions": shelf_actions,
"date_today": timezone.localdate().isoformat(),
},
)

View file

@ -70,9 +70,7 @@ def profile(request: AuthedHttpRequest, user_name):
.order_by("-created_time")
)
shelf_list[category]["reviewed"] = {
"title": _("{shelf_label} {item_category}").format(
shelf_label="reviewed", item_category=category.label
),
"title": target.shelf_manager.get_label("reviewed", category),
"count": reviews.count(),
"members": reviews[:10].prefetch_related("item"),
}

File diff suppressed because it is too large Load diff

View file

@ -520,8 +520,11 @@ def share_comment(comment):
if user.preference.mastodon_append_tag
else ""
)
action = ShelfManager.get_action_label(ShelfType.PROGRESS, comment.item.category)
content = f"{action} {comment.item.display_title}\n{comment.text}\n{comment.item.absolute_url}{tags}"
tpl = ShelfManager.get_action_template(ShelfType.PROGRESS, comment.item.category)
content = (
_(tpl).format(item=comment.item.display_title)
+ f"\n{comment.text}\n{comment.item.absolute_url}{tags}"
)
update_id = None
if comment.metadata.get(
"shared_link"
@ -562,7 +565,9 @@ def share_mark(mark, post_as_new=False):
site.star_mode if site else 0,
)
spoiler_text, txt = get_spoiler_text(mark.comment_text or "", mark.item)
content = f"{mark.action_label_for_feed}{mark.item.display_title}{stars}\n{mark.item.absolute_url}\n{txt}{tags}"
content = (
f"{mark.get_action_for_feed()} {stars}\n{mark.item.absolute_url}\n{txt}{tags}"
)
update_id = (
None
if post_as_new
@ -590,6 +595,7 @@ def share_mark(mark, post_as_new=False):
def share_review(review):
from catalog.common import ItemCategory
from journal.models import ShelfManager
user = review.owner.user
visibility = get_toot_visibility(review.visibility, user)
@ -601,9 +607,11 @@ def share_review(review):
if user.preference.mastodon_append_tag
else ""
)
tpl = ShelfManager.get_action_template("reviewed", review.item.category)
content = (
"wrote a review of {item_title}".format(item_title=review.item.display_title)
+ "\n{review.title}\n{review.absolute_url}{tags}"
_(tpl).format(item=review.item.display_title)
+ "\n{review.title}\n{review.absolute_url} "
+ tags
)
update_id = None
if review.metadata.get(

View file

@ -6,4 +6,4 @@ djlint~=1.34.0
isort~=5.12.0
lxml-stubs
pre-commit
pyright==1.1.344
pyright==1.1.350

View file

@ -36,7 +36,7 @@
{% endif %}
</span>
<div class="spacing">
写了评论
{% blocktrans with item=activity.action_object.item.title %}wrote a review of {{item}}{% endblocktrans %}
<a href="{{ activity.action_object.url }}">{{ activity.action_object.title }}</a>
{% if activity.action_object.mark.rating_grade %}
{{ activity.action_object.mark.rating_grade | rating_star }}

View file

@ -591,11 +591,14 @@ class Takahe:
else ""
)
item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{comment.item_url}"
action = ShelfManager.get_action_label(
tpl = ShelfManager.get_action_template(
ShelfType.PROGRESS, comment.item.category
)
pre_conetent = (
f'{action} <a href="{item_link}">{comment.item.display_title}</a><br>'
_(tpl).format(
item=f'<a href="{item_link}">{comment.item.display_title}</a>'
)
+ "<br>"
)
spoiler, txt = Takahe.get_spoiler_text(comment.text, comment.item)
content = f"{txt}\n{tags}"
@ -626,6 +629,7 @@ class Takahe:
@staticmethod
def post_review(review, share_as_new_post: bool) -> Post | None:
from catalog.common import ItemCategory
from journal.models import ShelfManager
user = review.owner.user
tags = (
@ -639,10 +643,9 @@ class Takahe:
stars = _rating_to_emoji(review.rating_grade, 1)
item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{review.item.url}"
tpl = ShelfManager.get_action_template("reviewed", review.item.category)
pre_conetent = (
"wrote a review of {item_title}".format(
item_title=f'<a href="{item_link}">{review.item.display_title}</a>'
)
_(tpl).format(item=f'<a href="{item_link}">{review.item.display_title}</a>')
+ f'<br><a href="{review.absolute_url}">{review.title}</a>'
)
content = f"{stars}\n{tags}"
@ -685,7 +688,7 @@ class Takahe:
)
stars = _rating_to_emoji(mark.rating_grade, 1)
item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{mark.item.url}"
pre_conetent = f'{mark.action_label_for_feed} <a href="{item_link}">{mark.item.display_title}</a>'
pre_conetent = mark.get_action_for_feed(item_link=item_link)
spoiler, txt = Takahe.get_spoiler_text(mark.comment_text, mark.item)
content = f"{stars} \n{txt}\n{tags}"
data = {

View file

@ -57,6 +57,11 @@ def preferences(request):
"hidden_categories",
]
)
lang = request.POST.get("language")
print(lang)
if lang in dict(settings.LANGUAGES).keys() and lang != request.user.language:
request.user.language = lang
request.user.save(update_fields=["language"])
clear_preference_cache(request)
return render(
request,

16
users/middlewares.py Normal file
View file

@ -0,0 +1,16 @@
from django.conf import settings
from django.utils import translation
from django.utils.deprecation import MiddlewareMixin
class LanguageMiddleware(MiddlewareMixin):
def process_request(self, request):
user_language = settings.LANGUAGE_CODE
user = getattr(request, "user", None)
if user and user.is_authenticated:
user_language = getattr(user, "language", "")
if user_language not in dict(settings.LANGUAGES).keys():
user_language = settings.LANGUAGE_CODE
current_language = translation.get_language()
if user_language != current_language:
translation.activate(user_language)

View file

@ -0,0 +1,23 @@
# Generated by Django 4.2.10 on 2024-04-04 01:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("users", "0019_task"),
]
operations = [
migrations.AddField(
model_name="user",
name="language",
field=models.CharField(
choices=[("en", "English"), ("zh-hans", "Simplified Chinese")],
default="en",
max_length=10,
verbose_name="language",
),
),
]

View file

@ -4,6 +4,7 @@ from functools import cached_property
from typing import TYPE_CHECKING, ClassVar
import httpx
from django.conf import settings
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.exceptions import ValidationError
@ -91,6 +92,13 @@ class User(AbstractUser):
pending_email = models.EmailField(
_("email address pending verification"), default=None, null=True
)
language = models.CharField(
_("language"),
max_length=10,
choices=settings.LANGUAGES,
null=False,
default="en",
)
local_following = models.ManyToManyField(
through="Follow",
to="self",
@ -431,6 +439,8 @@ class User(AbstractUser):
from .preference import Preference
new_user = cls(**param)
if "language" not in param:
new_user.language = settings.LANGUAGE_CODE
new_user.save()
Preference.objects.create(user=new_user)
if new_user.username: # TODO make username required in registeration

View file

@ -134,6 +134,16 @@
placeholder="例如 #我的书影音"
value="{{ request.user.preference.mastodon_append_tag }}">
</fieldset>
<fieldset>
{% all_languages as languages %}
<label>{% trans 'language' %}</label>
<select name="language">
{% for lang in languages %}
<option value="{{ lang.0 }}"
{% if lang.0 == request.user.language %}selected{% endif %}>{{ lang.1 }}</option>
{% endfor %}
</select>
</fieldset>
<fieldset>
<legend>{% trans '搜索时不显示以下类型:' %}</legend>
<select name="hidden_categories" size="3" multiple>