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 # test coverage
/.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 RUN busybox --install
# postgresql and redis cli are not required, but install for development convenience # 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 useradd -U app
RUN rm -rf /var/lib/apt/lists/* RUN rm -rf /var/lib/apt/lists/*
COPY --from=build /neodb /neodb COPY --from=build /neodb /neodb
WORKDIR /neodb WORKDIR /neodb
COPY --from=build /neodb-venv /neodb-venv 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 compilescss
RUN NEODB_SECRET_KEY="t" NEODB_SITE_DOMAIN="x.y" NEODB_SITE_NAME="z" /neodb-venv/bin/python3 manage.py collectstatic --noinput 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 # Links in site footer
NEODB_SITE_LINKS=(dict, {}), NEODB_SITE_LINKS=(dict, {}),
# Default language
NEODB_LANGUAGE=(str, "zh-hans"),
# Invite only mode # Invite only mode
# when True: user will not be able to register unless with invite token # when True: user will not be able to register unless with invite token
# (generated by `neodb-manage invite --create`) # (generated by `neodb-manage invite --create`)
@ -323,7 +325,8 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"hijack.middleware.HijackUserMiddleware", "hijack.middleware.HijackUserMiddleware",
"django.middleware.locale.LocaleMiddleware", # "django.middleware.locale.LocaleMiddleware",
"users.middlewares.LanguageMiddleware",
"tz_detect.middleware.TimezoneMiddleware", "tz_detect.middleware.TimezoneMiddleware",
"auditlog.middleware.AuditlogMiddleware", "auditlog.middleware.AuditlogMiddleware",
# "maintenance_mode.middleware.MaintenanceModeMiddleware", # this should be last if enabled # "maintenance_mode.middleware.MaintenanceModeMiddleware", # this should be last if enabled
@ -384,15 +387,12 @@ if SLACK_TOKEN:
MARKDOWNX_MARKDOWNIFY_FUNCTION = "journal.models.render_md" MARKDOWNX_MARKDOWNIFY_FUNCTION = "journal.models.render_md"
# Internationalization LANGUAGE_CODE = env("NEODB_LANGUAGE", default="zh-hans") # type: ignore
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = "zh-hans"
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
# LANGUAGES = ( LANGUAGES = (
# ("en", _("English")), # ("en", _("English")),
# ("zh-hans", _("Simplified Chinese")), ("zh-hans", _("Simplified Chinese")),
# ) )
TIME_ZONE = env("NEODB_TIMEZONE", default="Asia/Shanghai") # type: ignore TIME_ZONE = env("NEODB_TIMEZONE", default="Asia/Shanghai") # type: ignore

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -71,6 +71,21 @@ class Mark:
def action_label_for_feed(self) -> str: def action_label_for_feed(self) -> str:
return str(self.action_label) 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 @property
def shelf_label(self) -> str | None: def shelf_label(self) -> str | None:
return ( return (

View file

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

View file

@ -24,44 +24,253 @@ class ShelfType(models.TextChoices):
DROPPED = ("dropped", _("DROPPED")) DROPPED = ("dropped", _("DROPPED"))
SHELF_LABELS = [ _REVIEWED = "reviewed"
[ItemCategory.Book, ShelfType.WISHLIST, _("wants to read")],
[ItemCategory.Book, ShelfType.PROGRESS, _("started reading")], _SHELF_LABELS = [
[ItemCategory.Book, ShelfType.COMPLETE, _("finished reading")], [
[ItemCategory.Book, ShelfType.DROPPED, _("stopped reading")], ItemCategory.Book,
[ItemCategory.Movie, ShelfType.WISHLIST, _("wants to watch")], ShelfType.WISHLIST,
[ItemCategory.Movie, ShelfType.PROGRESS, _("started watching")], _("books to read"),
[ItemCategory.Movie, ShelfType.COMPLETE, _("finished watching")], _("want to read"),
[ItemCategory.Movie, ShelfType.DROPPED, _("stopped watching")], _("wants to read {item}"),
[ItemCategory.TV, ShelfType.WISHLIST, _("wants to watch")], ],
[ItemCategory.TV, ShelfType.PROGRESS, _("started watching")], [
[ItemCategory.TV, ShelfType.COMPLETE, _("finished watching")], ItemCategory.Book,
[ItemCategory.TV, ShelfType.DROPPED, _("stopped watching")], ShelfType.PROGRESS,
[ItemCategory.Music, ShelfType.WISHLIST, _("wants to listen")], _("books reading"),
[ItemCategory.Music, ShelfType.PROGRESS, _("started listening")], _("start reading"),
[ItemCategory.Music, ShelfType.COMPLETE, _("finished listening")], _("started reading {item}"),
[ItemCategory.Music, ShelfType.DROPPED, _("stopped listening")], ],
[ItemCategory.Game, ShelfType.WISHLIST, _("wants to play")], [
[ItemCategory.Game, ShelfType.PROGRESS, _("started playing")], ItemCategory.Book,
[ItemCategory.Game, ShelfType.COMPLETE, _("finished playing")], ShelfType.COMPLETE,
[ItemCategory.Game, ShelfType.DROPPED, _("stopped playing")], _("books completed"),
[ItemCategory.Podcast, ShelfType.WISHLIST, _("wants to listen")], _("finish reading"),
[ItemCategory.Podcast, ShelfType.PROGRESS, _("started listening")], _("finished reading {item}"),
[ItemCategory.Podcast, ShelfType.COMPLETE, _("finished listening")], ],
[ItemCategory.Podcast, ShelfType.DROPPED, _("stopped listening")], [
[ItemCategory.Performance, ShelfType.WISHLIST, _("wants to see")], 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 # disable progress shelf for Performance
[ItemCategory.Performance, ShelfType.PROGRESS, ""], [ItemCategory.Performance, ShelfType.PROGRESS, "", "", ""],
[ItemCategory.Performance, ShelfType.COMPLETE, _("finished seeing")], [
[ItemCategory.Performance, ShelfType.DROPPED, _("stopped seeing")], 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 # 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): class ShelfMember(ListMember):
parent = models.ForeignKey( parent = models.ForeignKey(
"Shelf", related_name="members", on_delete=models.CASCADE "Shelf", related_name="members", on_delete=models.CASCADE
@ -284,23 +493,39 @@ class ShelfManager:
# ) # )
# return shelf.members.all().order_by # 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 @classmethod
def get_action_label( def get_action_label(
cls, shelf_type: ShelfType | str, item_category: ItemCategory cls, shelf_type: ShelfType | str, item_category: ItemCategory
) -> str: ) -> str:
st = str(shelf_type) 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 return sts[0] if sts else st
@classmethod @classmethod
def get_label(cls, shelf_type: ShelfType, item_category: ItemCategory): def get_action_template(
ic = ItemCategory(item_category).label cls, shelf_type: ShelfType | str, item_category: ItemCategory
st = cls.get_action_label(shelf_type, item_category) ) -> str:
return ( st = str(shelf_type)
_("{shelf_label} {item_category}").format(shelf_label=st, item_category=ic) sts = [n[4] for n in _SHELF_LABELS if n[0] == item_category and n[1] == st]
if st return sts[0] if sts else st
else None
)
@staticmethod @staticmethod
def get_manager_for_user(owner: APIdentity): def get_manager_for_user(owner: APIdentity):

View file

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

View file

@ -102,7 +102,9 @@ def render_list(
page_number = int(request.GET.get("page", default=1)) page_number = int(request.GET.get("page", default=1))
members = paginator.get_page(page_number) members = paginator.get_page(page_number)
pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET) 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( return render(
request, request,
f"user_{type}_list.html", 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 mastodon.api import boost_toot_later, share_comment
from takahe.utils import Takahe 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 from .common import render_list, render_relogin, target_identity_required
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -45,7 +45,7 @@ def mark(request: AuthedHttpRequest, item_uuid):
mark = Mark(request.user.identity, item) mark = Mark(request.user.identity, item)
if request.method == "GET": if request.method == "GET":
tags = request.user.identity.tag_manager.get_item_tags(item) 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) shelf_type = request.GET.get("shelf_type", mark.shelf_type)
return render( return render(
request, request,
@ -55,7 +55,7 @@ def mark(request: AuthedHttpRequest, item_uuid):
"mark": mark, "mark": mark,
"shelf_type": shelf_type, "shelf_type": shelf_type,
"tags": ",".join(tags), "tags": ",".join(tags),
"shelf_labels": shelf_labels, "shelf_actions": shelf_actions,
"date_today": timezone.localdate().isoformat(), "date_today": timezone.localdate().isoformat(),
}, },
) )

View file

@ -70,9 +70,7 @@ def profile(request: AuthedHttpRequest, user_name):
.order_by("-created_time") .order_by("-created_time")
) )
shelf_list[category]["reviewed"] = { shelf_list[category]["reviewed"] = {
"title": _("{shelf_label} {item_category}").format( "title": target.shelf_manager.get_label("reviewed", category),
shelf_label="reviewed", item_category=category.label
),
"count": reviews.count(), "count": reviews.count(),
"members": reviews[:10].prefetch_related("item"), "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 if user.preference.mastodon_append_tag
else "" else ""
) )
action = ShelfManager.get_action_label(ShelfType.PROGRESS, comment.item.category) tpl = ShelfManager.get_action_template(ShelfType.PROGRESS, comment.item.category)
content = f"{action} {comment.item.display_title}\n{comment.text}\n{comment.item.absolute_url}{tags}" content = (
_(tpl).format(item=comment.item.display_title)
+ f"\n{comment.text}\n{comment.item.absolute_url}{tags}"
)
update_id = None update_id = None
if comment.metadata.get( if comment.metadata.get(
"shared_link" "shared_link"
@ -562,7 +565,9 @@ def share_mark(mark, post_as_new=False):
site.star_mode if site else 0, site.star_mode if site else 0,
) )
spoiler_text, txt = get_spoiler_text(mark.comment_text or "", mark.item) 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 = ( update_id = (
None None
if post_as_new if post_as_new
@ -590,6 +595,7 @@ def share_mark(mark, post_as_new=False):
def share_review(review): def share_review(review):
from catalog.common import ItemCategory from catalog.common import ItemCategory
from journal.models import ShelfManager
user = review.owner.user user = review.owner.user
visibility = get_toot_visibility(review.visibility, user) visibility = get_toot_visibility(review.visibility, user)
@ -601,9 +607,11 @@ def share_review(review):
if user.preference.mastodon_append_tag if user.preference.mastodon_append_tag
else "" else ""
) )
tpl = ShelfManager.get_action_template("reviewed", review.item.category)
content = ( content = (
"wrote a review of {item_title}".format(item_title=review.item.display_title) _(tpl).format(item=review.item.display_title)
+ "\n{review.title}\n{review.absolute_url}{tags}" + "\n{review.title}\n{review.absolute_url} "
+ tags
) )
update_id = None update_id = None
if review.metadata.get( if review.metadata.get(

View file

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

View file

@ -36,7 +36,7 @@
{% endif %} {% endif %}
</span> </span>
<div class="spacing"> <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> <a href="{{ activity.action_object.url }}">{{ activity.action_object.title }}</a>
{% if activity.action_object.mark.rating_grade %} {% if activity.action_object.mark.rating_grade %}
{{ activity.action_object.mark.rating_grade | rating_star }} {{ activity.action_object.mark.rating_grade | rating_star }}

View file

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

View file

@ -57,6 +57,11 @@ def preferences(request):
"hidden_categories", "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) clear_preference_cache(request)
return render( return render(
request, 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 from typing import TYPE_CHECKING, ClassVar
import httpx import httpx
from django.conf import settings
from django.contrib.auth.models import AbstractUser, BaseUserManager from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -91,6 +92,13 @@ class User(AbstractUser):
pending_email = models.EmailField( pending_email = models.EmailField(
_("email address pending verification"), default=None, null=True _("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( local_following = models.ManyToManyField(
through="Follow", through="Follow",
to="self", to="self",
@ -431,6 +439,8 @@ class User(AbstractUser):
from .preference import Preference from .preference import Preference
new_user = cls(**param) new_user = cls(**param)
if "language" not in param:
new_user.language = settings.LANGUAGE_CODE
new_user.save() new_user.save()
Preference.objects.create(user=new_user) Preference.objects.create(user=new_user)
if new_user.username: # TODO make username required in registeration if new_user.username: # TODO make username required in registeration

View file

@ -134,6 +134,16 @@
placeholder="例如 #我的书影音" placeholder="例如 #我的书影音"
value="{{ request.user.preference.mastodon_append_tag }}"> value="{{ request.user.preference.mastodon_append_tag }}">
</fieldset> </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> <fieldset>
<legend>{% trans '搜索时不显示以下类型:' %}</legend> <legend>{% trans '搜索时不显示以下类型:' %}</legend>
<select name="hidden_categories" size="3" multiple> <select name="hidden_categories" size="3" multiple>