diff --git a/common/templatetags/prettydate.py b/common/templatetags/prettydate.py index 05725c23..538adb58 100644 --- a/common/templatetags/prettydate.py +++ b/common/templatetags/prettydate.py @@ -1,5 +1,6 @@ from django import template from django.utils import timezone +from django.utils.translation import gettext as _ register = template.Library() @@ -10,12 +11,12 @@ def prettydate(d): diff = timezone.now() - d s = diff.seconds if diff.days > 14 or diff.days < 0: - return d.strftime("%Y年%m月%d日") + return d.strftime("%Y-%m-%d") elif diff.days >= 1: - return "{} 天前".format(diff.days) + return "{}d".format(diff.days) elif s < 120: - return "刚刚" + return _("just now") elif s < 3600: - return "{} 分钟前".format(s // 60) + return "{}m".format(s // 60) else: - return "{} 小时前".format(s // 3600) + return "{}h".format(s // 3600) diff --git a/journal/models/collection.py b/journal/models/collection.py index 4b260e15..bc637d37 100644 --- a/journal/models/collection.py +++ b/journal/models/collection.py @@ -22,7 +22,7 @@ class CollectionMember(ListMember): "Collection", related_name="members", on_delete=models.CASCADE ) - note = jsondata.CharField(_("备注"), null=True, blank=True) + note = jsondata.CharField(_("note"), null=True, blank=True) @property def ap_object(self): @@ -45,8 +45,8 @@ class Collection(List): catalog_item = models.OneToOneField( CatalogCollection, on_delete=models.PROTECT, related_name="journal_item" ) - title = models.CharField(_("标题"), max_length=1000, default="") - brief = models.TextField(_("简介"), blank=True, default="") + title = models.CharField(_("title"), max_length=1000, default="") + brief = models.TextField(_("description"), blank=True, default="") cover = models.ImageField( upload_to=piece_cover_path, default=DEFAULT_ITEM_COVER, blank=True ) diff --git a/journal/models/common.py b/journal/models/common.py index 06679d0d..e73afd2b 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -23,9 +23,9 @@ if TYPE_CHECKING: class VisibilityType(models.IntegerChoices): - Public = 0, _("公开") - Follower_Only = 1, _("仅关注者") - Private = 2, _("仅自己") + Public = 0, _("Public") + Follower_Only = 1, _("Followers Only") + Private = 2, _("Mentioned Only") def q_owned_piece_visible_to_user(viewing_user: User, owner: APIdentity): diff --git a/journal/models/mark.py b/journal/models/mark.py index 9cc7a7d7..d732efe7 100644 --- a/journal/models/mark.py +++ b/journal/models/mark.py @@ -69,7 +69,7 @@ class Mark: @property def action_label_for_feed(self) -> str: - return re.sub(r"不(.+)了", r"不再\1", str(self.action_label)) # TODO i18n + return str(self.action_label) @property def shelf_label(self) -> str | None: diff --git a/journal/models/shelf.py b/journal/models/shelf.py index 68af7df0..0d8fb296 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -18,44 +18,44 @@ if TYPE_CHECKING: class ShelfType(models.TextChoices): - WISHLIST = ("wishlist", "未开始") - PROGRESS = ("progress", "进行中") - COMPLETE = ("complete", "完成") - DROPPED = ("dropped", "放弃") + WISHLIST = ("wishlist", _("WISHLIST")) + PROGRESS = ("progress", _("PROGRESS")) + COMPLETE = ("complete", _("COMPLETE")) + DROPPED = ("dropped", _("DROPPED")) SHELF_LABELS = [ - [ItemCategory.Book, ShelfType.WISHLIST, _("想读")], - [ItemCategory.Book, ShelfType.PROGRESS, _("在读")], - [ItemCategory.Book, ShelfType.COMPLETE, _("读过")], - [ItemCategory.Book, ShelfType.DROPPED, _("不读了")], - [ItemCategory.Movie, ShelfType.WISHLIST, _("想看")], - [ItemCategory.Movie, ShelfType.PROGRESS, _("在看")], - [ItemCategory.Movie, ShelfType.COMPLETE, _("看过")], - [ItemCategory.Movie, ShelfType.DROPPED, _("不看了")], - [ItemCategory.TV, ShelfType.WISHLIST, _("想看")], - [ItemCategory.TV, ShelfType.PROGRESS, _("在看")], - [ItemCategory.TV, ShelfType.COMPLETE, _("看过")], - [ItemCategory.TV, ShelfType.DROPPED, _("不看了")], - [ItemCategory.Music, ShelfType.WISHLIST, _("想听")], - [ItemCategory.Music, ShelfType.PROGRESS, _("在听")], - [ItemCategory.Music, ShelfType.COMPLETE, _("听过")], - [ItemCategory.Music, ShelfType.DROPPED, _("不听了")], - [ItemCategory.Game, ShelfType.WISHLIST, _("想玩")], - [ItemCategory.Game, ShelfType.PROGRESS, _("在玩")], - [ItemCategory.Game, ShelfType.COMPLETE, _("玩过")], - [ItemCategory.Game, ShelfType.DROPPED, _("不玩了")], - [ItemCategory.Podcast, ShelfType.WISHLIST, _("想听")], - [ItemCategory.Podcast, ShelfType.PROGRESS, _("在听")], - [ItemCategory.Podcast, ShelfType.COMPLETE, _("听过")], - [ItemCategory.Podcast, ShelfType.DROPPED, _("不听了")], - # disable all shelves for PodcastEpisode - [ItemCategory.Performance, ShelfType.WISHLIST, _("想看")], + [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")], # disable progress shelf for Performance [ItemCategory.Performance, ShelfType.PROGRESS, ""], - [ItemCategory.Performance, ShelfType.COMPLETE, _("看过")], - [ItemCategory.Performance, ShelfType.DROPPED, _("不看了")], + [ItemCategory.Performance, ShelfType.COMPLETE, _("finished seeing")], + [ItemCategory.Performance, ShelfType.DROPPED, _("stopped seeing")], ] +# grammatically problematic, for translation only def get_shelf_labels_for_category(item_category: ItemCategory): @@ -206,7 +206,7 @@ class ShelfLogEntry(models.Model): if self.shelf_type: return ShelfManager.get_action_label(self.shelf_type, self.item.category) else: - return _("移除标记") + return _("removed mark") def link_post_id(self, post_id: int): ShelfLogEntryPost.objects.get_or_create(log_entry=self, post_id=post_id) @@ -297,7 +297,7 @@ class ShelfManager: 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) + _("{shelf_label} {item_category}").format(shelf_label=st, item_category=ic) if st else None ) diff --git a/journal/views/collection.py b/journal/views/collection.py index e2b22df0..3f6ebdf5 100644 --- a/journal/views/collection.py +++ b/journal/views/collection.py @@ -40,7 +40,8 @@ def add_to_collection(request: AuthedHttpRequest, item_uuid): cid = int(request.POST.get("collection_id", default=0)) if not cid: cid = Collection.objects.create( - owner=request.user.identity, title=f"{request.user.display_name}的收藏单" + owner=request.user.identity, + title=_("Collection by {0}").format(request.user.display_name), ).id collection = Collection.objects.get(owner=request.user.identity, id=cid) collection.append_item(item, note=request.POST.get("note")) @@ -196,7 +197,7 @@ def collection_append_item(request: AuthedHttpRequest, collection_uuid): collection.save() msg = None else: - msg = _("条目链接无法识别,请输入本站已有条目的链接。") + msg = _("Unable to find the item, please use item url from this site.") return collection_retrieve_items(request, collection_uuid, True, msg) diff --git a/journal/views/common.py b/journal/views/common.py index cf870af9..03519bbf 100644 --- a/journal/views/common.py +++ b/journal/views/common.py @@ -29,16 +29,16 @@ def render_relogin(request): "common/error.html", { "url": reverse("users:connect") + "?domain=" + request.user.mastodon_site, - "msg": _("信息已保存,但是未能分享到联邦宇宙"), + "msg": _("Data saved but unable to repost to Fediverse."), "secondary_msg": _( - "可能是你在联邦宇宙(Mastodon/Pleroma/...)的登录状态过期了,正在跳转到联邦宇宙重新登录😼" + "Redirecting to your Mastodon instance now to re-authenticate." ), }, ) def render_list_not_found(request): - msg = _("相关列表不存在") + msg = _("List not found.") return render( request, "common/error.html", diff --git a/journal/views/mark.py b/journal/views/mark.py index 129146f4..a5069f10 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -106,12 +106,16 @@ def mark(request: AuthedHttpRequest, item_uuid): return render_relogin(request) except ValueError as e: _logger.warn(f"post to mastodon error {e} {request.user}") - err = _("内容长度超出实例限制") if str(e) == "422" else str(e) + err = ( + _("Content too long for your Mastodon instance.") + if str(e) == "422" + else str(e) + ) return render( request, "common/error.html", { - "msg": _("标记已保存,但是未能分享到联邦宇宙"), + "msg": _("Data saved but unable to repost to Fediverse."), "secondary_msg": err, }, ) @@ -140,7 +144,7 @@ def mark_log(request: AuthedHttpRequest, item_uuid, log_id): def comment(request: AuthedHttpRequest, item_uuid): item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) if not item.class_name in ["podcastepisode", "tvepisode"]: - raise BadRequest("不支持评论此类型的条目") + raise BadRequest("Commenting this type of items is not supported yet.") comment = Comment.objects.filter(owner=request.user.identity, item=item).first() if request.method == "GET": return render( diff --git a/journal/views/profile.py b/journal/views/profile.py index a96f9a5a..fa58b5ee 100644 --- a/journal/views/profile.py +++ b/journal/views/profile.py @@ -70,7 +70,9 @@ def profile(request: AuthedHttpRequest, user_name): .order_by("-created_time") ) shelf_list[category]["reviewed"] = { - "title": "评论过的" + category.label, + "title": _("{shelf_label} {item_category}").format( + shelf_label="reviewed", item_category=category.label + ), "count": reviews.count(), "members": reviews[:10].prefetch_related("item"), } diff --git a/journal/views/review.py b/journal/views/review.py index 5e6814d3..f04dc797 100644 --- a/journal/views/review.py +++ b/journal/views/review.py @@ -109,18 +109,22 @@ class ReviewFeed(Feed): return APIdentity.get_by_handle(kwargs["username"]) def title(self, owner): - return "%s的评论" % owner.display_name if owner else "无效链接" + return ( + _("Reviews by {0}").format(owner.display_name) + if owner + else _("link unavailable") + ) def link(self, owner): return owner.url if owner else settings.SITE_INFO["site_url"] def description(self, owner): if not owner: - return "无效链接" + return _("link unavailable") elif not owner.anonymous_viewable: - return "该用户已关闭匿名查看" + return _("anonymous access disabled by owner") else: - return "%s的评论合集 - NeoDB" % owner.display_name + return _("Reviews by {0}").format(owner.display_name) def items(self, owner): if owner is None or not owner.anonymous_viewable: @@ -129,7 +133,9 @@ class ReviewFeed(Feed): return reviews def item_title(self, item: Review): - return f"{item.title} - 评论《{item.item.title}》" + return _("{review_title} - a review of {item_title}").format( + review_title=item.title, item_title=item.item.title + ) def item_description(self, item: Review): target_html = ( diff --git a/journal/views/tag.py b/journal/views/tag.py index 7e83c6ad..bfee5828 100644 --- a/journal/views/tag.py +++ b/journal/views/tag.py @@ -56,11 +56,11 @@ def user_tag_edit(request): else None ) if not tag or not tag_title: - msg.error(request.user, _("无效标签")) + msg.error(request.user, _("Invalid tag.")) return HttpResponseRedirect(request.META.get("HTTP_REFERER")) if request.POST.get("delete"): tag.delete() - msg.info(request.user, _("标签已删除")) + msg.info(request.user, _("Tag deleted.")) return redirect( reverse("journal:user_tag_list", args=[request.user.username]) ) @@ -70,13 +70,13 @@ def user_tag_edit(request): owner=request.user.identity, title=tag_title ).exists() ): - msg.error(request.user, _("标签已存在")) + msg.error(request.user, _("Duplicated tag.")) return HttpResponseRedirect(request.META.get("HTTP_REFERER")) tag.title = tag_title tag.visibility = int(request.POST.get("visibility", 0)) tag.visibility = 0 if tag.visibility == 0 else 2 tag.save() - msg.info(request.user, _("标签已修改")) + msg.info(request.user, _("Tag updated.")) return redirect( reverse( "journal:user_tag_member_list", diff --git a/journal/views/wrapped.py b/journal/views/wrapped.py index d6016274..2291fb62 100644 --- a/journal/views/wrapped.py +++ b/journal/views/wrapped.py @@ -138,5 +138,5 @@ class WrappedShareView(LoginRequiredMixin, TemplateView): ) elif post: boost_toot_later(user, post.url) - messages.add_message(request, messages.INFO, _("已分享到时间轴。")) + messages.add_message(request, messages.INFO, _("Summary posted to timeline.")) return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) diff --git a/mastodon/api.py b/mastodon/api.py index bd786d9d..c9f80971 100644 --- a/mastodon/api.py +++ b/mastodon/api.py @@ -1,5 +1,4 @@ import functools -import html import random import re import string @@ -9,6 +8,7 @@ from urllib.parse import quote import django_rq import requests from django.conf import settings +from django.utils.translation import gettext_lazy as _ from loguru import logger from mastodon.utils import rating_to_emoji @@ -350,7 +350,7 @@ def detect_server_info(login_domain) -> tuple[str, str, str]: try: response = get(url, headers={"User-Agent": USER_AGENT}) j = response.json() - except: + except Exception: api_domain = login_domain logger.info( f"detect_server_info: {login_domain} {domain} {api_domain} {server_version}" @@ -487,7 +487,9 @@ def get_status_id_by_url(url): def get_spoiler_text(text, item): if text.find(">!") != -1: - spoiler_text = f"关于《{item.display_title}》 可能有关键情节等敏感内容" + spoiler_text = _( + "regarding {item_title}, may contain spoiler or triggering content" + ).format(item_title=item.display_title) return spoiler_text, text.replace(">!", "").replace("!<", "") else: return None, text @@ -506,6 +508,7 @@ def get_toot_visibility(visibility, user): def share_comment(comment): from catalog.common import ItemCategory + from journal.models import ShelfManager, ShelfType user = comment.owner.user visibility = get_toot_visibility(comment.visibility, user) @@ -517,7 +520,8 @@ def share_comment(comment): if user.preference.mastodon_append_tag else "" ) - content = f"评论《{comment.item.display_title}》\n{comment.text}\n{comment.item.absolute_url}{tags}" + 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}" update_id = None if comment.metadata.get( "shared_link" @@ -597,7 +601,10 @@ def share_review(review): if user.preference.mastodon_append_tag else "" ) - content = f"发布了关于《{review.item.display_title}》的评论\n{review.title}\n{review.absolute_url}{tags}" + content = ( + "wrote a review of {item_title}".format(item_title=review.item.display_title) + + "\n{review.title}\n{review.absolute_url}{tags}" + ) update_id = None if review.metadata.get( "shared_link" @@ -622,20 +629,23 @@ def share_review(review): def share_collection(collection, comment, user, visibility_no, link): visibility = get_toot_visibility(visibility_no, user) tags = ( - "\n" + user.preference.mastodon_append_tag.replace("[category]", "收藏单") + "\n" + + user.preference.mastodon_append_tag.replace("[category]", _("collection")) if user.preference.mastodon_append_tag else "" ) user_str = ( - "我" + _("shared my collection") if user == collection.owner.user else ( - " @" + collection.owner.user.mastodon_acct + " " - if collection.owner.user.mastodon_acct - else " " + collection.owner.username + " " + _("shared {username}'s collection").format( + username=" @" + collection.owner.user.mastodon_acct + " " + if collection.owner.user.mastodon_acct + else " " + collection.owner.username + " " + ) ) ) - content = f"分享{user_str}的收藏单《{collection.title}》\n{link}\n{comment}{tags}" + content = f"{user_str}:{collection.title}\n{link}\n{comment}{tags}" response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token) if response is not None and response.status_code in [200, 201]: return True diff --git a/mastodon/decorators.py b/mastodon/decorators.py index a7167f0d..03f6ce0f 100644 --- a/mastodon/decorators.py +++ b/mastodon/decorators.py @@ -15,7 +15,9 @@ def mastodon_request_included(func): return func(*args, **kwargs) except (Timeout, ConnectionError): return render( - args[0], "common/error.html", {"msg": _("联邦宇宙请求超时叻_(´ཀ`」 ∠)__ ")} + args[0], + "common/error.html", + {"msg": _("Timeout connecting to Fediverse.")}, ) return wrapper diff --git a/social/templates/feed_data.html b/social/templates/feed_data.html index 6ff481c7..62d33088 100644 --- a/social/templates/feed_data.html +++ b/social/templates/feed_data.html @@ -1,6 +1,7 @@ {% load static %} {% load i18n %} {% load l10n %} +{% load humanize %} {% load admin_url %} {% load mastodon %} {% load oauth_token %} @@ -17,7 +18,7 @@
- {{ activity.action_object.created_time|prettydate }} + {{ activity.action_object.created_time|naturaltime }}
diff --git a/takahe/utils.py b/takahe/utils.py index 05910758..4b5b92b0 100644 --- a/takahe/utils.py +++ b/takahe/utils.py @@ -5,6 +5,7 @@ import blurhash from django.conf import settings from django.core.cache import cache from django.core.files.images import ImageFile +from django.utils.translation import gettext_lazy as _ from PIL import Image from .models import * @@ -18,7 +19,7 @@ if TYPE_CHECKING: def _int(s: str): try: return int(s) - except: + except Exception: return -1 @@ -509,7 +510,9 @@ class Takahe: @staticmethod def get_spoiler_text(text, item): if text and text.find(">!") != -1: - spoiler_text = f"关于《{item.display_title}》 可能有关键情节等敏感内容" + spoiler_text = _( + "regarding {item_title}, may contain spoiler or triggering content" + ).format(item_title=item.display_title) return spoiler_text, text.replace(">!", "").replace("!<", "") else: return None, text or "" @@ -547,12 +550,9 @@ class Takahe: } if existing_post and existing_post.type_data == data: return existing_post - action_label = "创建" - category = "收藏单" + action = _("created collection") item_link = collection.absolute_url - pre_conetent = ( - f'{action_label}{category} {collection.title}
' - ) + pre_conetent = f'{action} {collection.title}
' content = collection.plain_content if len(content) > 360: content = content[:357] + "..." @@ -581,6 +581,7 @@ class Takahe: @staticmethod def post_comment(comment, share_as_new_post: bool) -> Post | None: from catalog.common import ItemCategory + from journal.models import ShelfManager, ShelfType user = comment.owner.user category = str(ItemCategory(comment.item.category).label) @@ -590,8 +591,12 @@ class Takahe: else "" ) item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{comment.item_url}" - action_label = "评论" if comment.text else "分享" - pre_conetent = f'{action_label}{category} {comment.item.display_title}
' + action = ShelfManager.get_action_label( + ShelfType.PROGRESS, comment.item.category + ) + pre_conetent = ( + f'{action} {comment.item.display_title}
' + ) spoiler, txt = Takahe.get_spoiler_text(comment.text, comment.item) content = f"{txt}\n{tags}" data = { @@ -634,7 +639,12 @@ class Takahe: stars = _rating_to_emoji(review.rating_grade, 1) item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{review.item.url}" - pre_conetent = f'发布了关于 {review.item.display_title} 的评论:
{review.title}' + pre_conetent = ( + "wrote a review of {item_title}".format( + item_title=f'{review.item.display_title}' + ) + + f'
{review.title}' + ) content = f"{stars}\n{tags}" data = { "object": {