basic i18n support
This commit is contained in:
parent
3e301ad453
commit
fca9b6155d
26 changed files with 2709 additions and 90 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -42,3 +42,7 @@ log
|
|||
|
||||
# test coverage
|
||||
/.coverage
|
||||
|
||||
|
||||
# translations
|
||||
*.mo
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
LANGUAGES = (
|
||||
# ("en", _("English")),
|
||||
# ("zh-hans", _("Simplified Chinese")),
|
||||
# )
|
||||
("zh-hans", _("Simplified Chinese")),
|
||||
)
|
||||
|
||||
TIME_ZONE = env("NEODB_TIMEZONE", default="Asia/Shanghai") # type: ignore
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }}"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
2301
locale/zh_Hans/LC_MESSAGES/django.po
Normal file
2301
locale/zh_Hans/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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(
|
||||
|
|
|
@ -6,4 +6,4 @@ djlint~=1.34.0
|
|||
isort~=5.12.0
|
||||
lxml-stubs
|
||||
pre-commit
|
||||
pyright==1.1.344
|
||||
pyright==1.1.350
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
16
users/middlewares.py
Normal 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)
|
23
users/migrations/0020_user_language.py
Normal file
23
users/migrations/0020_user_language.py
Normal 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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue