diff --git a/.dockerignore b/.dockerignore
index dab01ceef..188cb3f45 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,8 +1,6 @@
bin/data/
# virtualenv
venv/
-migrations/
-!migrations/__init__.py
collectedstatic/
djangoblog/whoosh_index/
uploads/
diff --git a/Dockerfile b/Dockerfile
index 03afb9b51..e2f7aabe4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,9 @@
FROM python:3
ENV PYTHONUNBUFFERED 1
WORKDIR /code/djangoblog/
-RUN apt-get install default-libmysqlclient-dev -y && \
- ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+RUN apt-get update && \
+ apt-get install default-libmysqlclient-dev gettext -y && \
+ rm -rf /var/lib/apt/lists/*
ADD requirements.txt requirements.txt
RUN pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \
diff --git a/accounts/admin.py b/accounts/admin.py
index c3fbf3532..32e483c08 100644
--- a/accounts/admin.py
+++ b/accounts/admin.py
@@ -1,6 +1,5 @@
from django import forms
from django.contrib.auth.admin import UserAdmin
-from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.forms import UsernameField
from django.utils.translation import gettext_lazy as _
@@ -10,8 +9,8 @@
class BlogUserCreationForm(forms.ModelForm):
- password1 = forms.CharField(label='密码', widget=forms.PasswordInput)
- password2 = forms.CharField(label='再次输入密码', widget=forms.PasswordInput)
+ password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
+ password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
model = BlogUser
@@ -22,7 +21,7 @@ def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
- raise forms.ValidationError("两次密码不一致")
+ raise forms.ValidationError(_("passwords do not match"))
return password2
def save(self, commit=True):
@@ -36,16 +35,6 @@ def save(self, commit=True):
class BlogUserChangeForm(UserChangeForm):
- password = ReadOnlyPasswordHashField(
- label=_("Password"),
- help_text=_(
- "Raw passwords are not stored, so there is no way to see this "
- "user's password, but you can change the password using "
- "this form."
- ),
- )
- email = forms.EmailField(label="Email", widget=forms.EmailInput)
-
class Meta:
model = BlogUser
fields = '__all__'
diff --git a/accounts/forms.py b/accounts/forms.py
index 70c492b16..fce4137e3 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -3,7 +3,7 @@
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.exceptions import ValidationError
from django.forms import widgets
-
+from django.utils.translation import gettext_lazy as _
from . import utils
from .models import BlogUser
@@ -33,7 +33,7 @@ def __init__(self, *args, **kwargs):
def clean_email(self):
email = self.cleaned_data['email']
if get_user_model().objects.filter(email=email).exists():
- raise ValidationError("该邮箱已经存在.")
+ raise ValidationError(_("email already exists"))
return email
class Meta:
@@ -43,11 +43,11 @@ class Meta:
class ForgetPasswordForm(forms.Form):
new_password1 = forms.CharField(
- label="新密码",
+ label=_("New password"),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
- 'placeholder': "密码"
+ 'placeholder': _("New password")
}
),
)
@@ -57,7 +57,7 @@ class ForgetPasswordForm(forms.Form):
widget=forms.PasswordInput(
attrs={
"class": "form-control",
- 'placeholder': "确认密码"
+ 'placeholder': _("Confirm password")
}
),
)
@@ -67,17 +67,17 @@ class ForgetPasswordForm(forms.Form):
widget=forms.TextInput(
attrs={
'class': 'form-control',
- 'placeholder': "邮箱"
+ 'placeholder': _("Email")
}
),
)
code = forms.CharField(
- label='验证码',
+ label=_('Code'),
widget=forms.TextInput(
attrs={
'class': 'form-control',
- 'placeholder': "验证码"
+ 'placeholder': _("Code")
}
),
)
@@ -86,7 +86,7 @@ def clean_new_password2(self):
password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2")
if password1 and password2 and password1 != password2:
- raise ValidationError("两次密码不一致")
+ raise ValidationError(_("passwords do not match"))
password_validation.validate_password(password2)
return password2
@@ -97,7 +97,7 @@ def clean_email(self):
email=user_email
).exists():
# todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
- raise ValidationError("未找到邮箱对应的用户")
+ raise ValidationError(_("email does not exist"))
return user_email
def clean_code(self):
@@ -113,5 +113,5 @@ def clean_code(self):
class ForgetPasswordCodeForm(forms.Form):
email = forms.EmailField(
- label="邮箱号"
+ label=_('Email'),
)
diff --git a/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
new file mode 100644
index 000000000..1a9f50956
--- /dev/null
+++ b/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
@@ -0,0 +1,46 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:13
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='bloguser',
+ options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
+ ),
+ migrations.RemoveField(
+ model_name='bloguser',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='bloguser',
+ name='last_mod_time',
+ ),
+ migrations.AddField(
+ model_name='bloguser',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='bloguser',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
+ ),
+ migrations.AlterField(
+ model_name='bloguser',
+ name='nickname',
+ field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
+ ),
+ migrations.AlterField(
+ model_name='bloguser',
+ name='source',
+ field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
+ ),
+ ]
diff --git a/accounts/models.py b/accounts/models.py
index 9f7454cd3..3baddbb2f 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -2,17 +2,17 @@
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
-
+from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
# Create your models here.
class BlogUser(AbstractUser):
- nickname = models.CharField('昵称', max_length=100, blank=True)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
- source = models.CharField("创建来源", max_length=100, blank=True)
+ nickname = models.CharField(_('nick name'), max_length=100, blank=True)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+ source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
return reverse(
@@ -30,6 +30,6 @@ def get_full_url(self):
class Meta:
ordering = ['-id']
- verbose_name = "用户"
+ verbose_name = _('user')
verbose_name_plural = verbose_name
get_latest_by = 'id'
diff --git a/accounts/tests.py b/accounts/tests.py
index ae3ae6987..a3085637c 100644
--- a/accounts/tests.py
+++ b/accounts/tests.py
@@ -1,11 +1,11 @@
-from django.conf import settings
from django.test import Client, RequestFactory, TestCase
from django.urls import reverse
from django.utils import timezone
+from django.utils.translation import gettext_lazy as _
-from djangoblog.utils import *
from accounts.models import BlogUser
from blog.models import Article, Category
+from djangoblog.utils import *
from . import utils
@@ -39,8 +39,8 @@ def test_validate_account(self):
category = Category()
category.name = "categoryaaa"
- category.created_time = timezone.now()
- category.last_mod_time = timezone.now()
+ category.creation_time = timezone.now()
+ category.last_modify_time = timezone.now()
category.save()
article = Article()
@@ -86,8 +86,8 @@ def test_validate_register(self):
delete_sidebar_cache()
category = Category()
category.name = "categoryaaa"
- category.created_time = timezone.now()
- category.last_mod_time = timezone.now()
+ category.creation_time = timezone.now()
+ category.last_modify_time = timezone.now()
category.save()
article = Article()
@@ -191,7 +191,7 @@ def test_forget_password_email_not_user(self):
response=resp,
form="form",
field="email",
- errors="未找到邮箱对应的用户"
+ errors=_("email does not exist")
)
def test_forget_password_email_code_error(self):
@@ -213,5 +213,5 @@ def test_forget_password_email_code_error(self):
response=resp,
form="form",
field="code",
- errors="验证码错误"
+ errors=_('Verification code error')
)
diff --git a/accounts/urls.py b/accounts/urls.py
index e11dd7358..107a801d1 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,5 +1,5 @@
-from django.urls import include, re_path
from django.urls import path
+from django.urls import re_path
from . import views
from .forms import LoginForm
@@ -7,22 +7,22 @@
app_name = "accounts"
urlpatterns = [re_path(r'^login/$',
- views.LoginView.as_view(success_url='/'),
- name='login',
- kwargs={'authentication_form': LoginForm}),
+ views.LoginView.as_view(success_url='/'),
+ name='login',
+ kwargs={'authentication_form': LoginForm}),
re_path(r'^register/$',
- views.RegisterView.as_view(success_url="/"),
- name='register'),
+ views.RegisterView.as_view(success_url="/"),
+ name='register'),
re_path(r'^logout/$',
- views.LogoutView.as_view(),
- name='logout'),
+ views.LogoutView.as_view(),
+ name='logout'),
path(r'account/result.html',
views.account_result,
name='result'),
re_path(r'^forget_password/$',
- views.ForgetPasswordView.as_view(),
- name='forget_password'),
+ views.ForgetPasswordView.as_view(),
+ name='forget_password'),
re_path(r'^forget_password_code/$',
- views.ForgetPasswordEmailCode.as_view(),
- name='forget_password_code'),
+ views.ForgetPasswordEmailCode.as_view(),
+ name='forget_password_code'),
]
diff --git a/accounts/utils.py b/accounts/utils.py
index 66886678c..4b94bdfe9 100644
--- a/accounts/utils.py
+++ b/accounts/utils.py
@@ -2,20 +2,24 @@
from datetime import timedelta
from django.core.cache import cache
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
from djangoblog.utils import send_email
_code_ttl = timedelta(minutes=5)
-def send_verify_email(to_mail: str, code: str, subject: str = "邮件验证码"):
+def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""发送重设密码验证码
Args:
to_mail: 接受邮箱
subject: 邮件主题
code: 验证码
"""
- html_content = f"您正在重设密码,验证码为:{code}, 5分钟内有效,请妥善保管"
+ html_content = _(
+ "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it "
+ "properly") % {'code': code}
send_email([to_mail], subject, html_content)
@@ -32,7 +36,7 @@ def verify(email: str, code: str) -> typing.Optional[str]:
"""
cache_code = get_code(email)
if cache_code != code:
- return "验证码错误"
+ return gettext("Verification code error")
def set_code(email: str, code: str):
diff --git a/accounts/views.py b/accounts/views.py
index 06b6fd772..ae67aec41 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,5 +1,5 @@
import logging
-
+from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
diff --git a/bin/docker_start.sh b/bin/docker_start.sh
index e599381b2..0be35a5c9 100644
--- a/bin/docker_start.sh
+++ b/bin/docker_start.sh
@@ -1,28 +1,25 @@
#!/usr/bin/env bash
-NAME="djangoblog" # Name of the application
-DJANGODIR=/code/djangoblog # Django project directory
-USER=root # the user to run as
-GROUP=root # the group to run as
-NUM_WORKERS=1 # how many worker processes should Gunicorn spawn
-#DJANGO_SETTINGS_MODULE=djangoblog.settings # which settings file should Django use
-DJANGO_WSGI_MODULE=djangoblog.wsgi # WSGI module name
+NAME="djangoblog"
+DJANGODIR=/code/djangoblog
+USER=root
+GROUP=root
+NUM_WORKERS=1
+DJANGO_WSGI_MODULE=djangoblog.wsgi
echo "Starting $NAME as `whoami`"
-# Activate the virtual environment
cd $DJANGODIR
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
-#pip install -Ur requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com && \
-# pip install gunicorn -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
+
python manage.py makemigrations && \
python manage.py migrate && \
python manage.py collectstatic --noinput && \
python manage.py compress --force && \
- python manage.py build_index
-# Start your Django Unicorn
-# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
+ python manage.py build_index && \
+ python manage.py compilemessages
+
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
diff --git a/blog/admin.py b/blog/admin.py
index 9a07b3d5b..5e1e03597 100644
--- a/blog/admin.py
+++ b/blog/admin.py
@@ -10,7 +10,7 @@
class ArticleListFilter(admin.SimpleListFilter):
- title = _("作者")
+ title = _("author")
parameter_name = 'author'
def lookups(self, request, model_admin):
@@ -50,10 +50,10 @@ def open_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='o')
-makr_article_publish.short_description = '发布选中文章'
-draft_article.short_description = '选中文章设置为草稿'
-close_article_commentstatus.short_description = '关闭文章评论'
-open_article_commentstatus.short_description = '打开文章评论'
+makr_article_publish.short_description = _('Publish selected articles')
+draft_article.short_description = _('Draft selected articles')
+close_article_commentstatus.short_description = _('Close article comments')
+open_article_commentstatus.short_description = _('Open article comments')
class ArticlelAdmin(admin.ModelAdmin):
@@ -65,7 +65,7 @@ class ArticlelAdmin(admin.ModelAdmin):
'title',
'author',
'link_to_category',
- 'created_time',
+ 'creation_time',
'views',
'status',
'type',
@@ -73,7 +73,7 @@ class ArticlelAdmin(admin.ModelAdmin):
list_display_links = ('id', 'title')
list_filter = (ArticleListFilter, 'status', 'type', 'category', 'tags')
filter_horizontal = ('tags',)
- exclude = ('created_time', 'last_mod_time')
+ exclude = ('creation_time', 'last_modify_time')
view_on_site = True
actions = [
makr_article_publish,
@@ -86,7 +86,7 @@ def link_to_category(self, obj):
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'%s' % (link, obj.category.name))
- link_to_category.short_description = '分类目录'
+ link_to_category.short_description = _('category')
def get_form(self, request, obj=None, **kwargs):
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
@@ -108,21 +108,21 @@ def get_view_on_site_url(self, obj=None):
class TagAdmin(admin.ModelAdmin):
- exclude = ('slug', 'last_mod_time', 'created_time')
+ exclude = ('slug', 'last_mod_time', 'creation_time')
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'parent_category', 'index')
- exclude = ('slug', 'last_mod_time', 'created_time')
+ exclude = ('slug', 'last_mod_time', 'creation_time')
class LinksAdmin(admin.ModelAdmin):
- exclude = ('last_mod_time', 'created_time')
+ exclude = ('last_mod_time', 'creation_time')
class SideBarAdmin(admin.ModelAdmin):
list_display = ('name', 'content', 'is_enable', 'sequence')
- exclude = ('last_mod_time', 'created_time')
+ exclude = ('last_mod_time', 'creation_time')
class BlogSettingsAdmin(admin.ModelAdmin):
diff --git a/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
new file mode 100644
index 000000000..d08e85341
--- /dev/null
+++ b/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
@@ -0,0 +1,300 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:13
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import mdeditor.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='article',
+ options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
+ ),
+ migrations.AlterModelOptions(
+ name='category',
+ options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
+ ),
+ migrations.AlterModelOptions(
+ name='links',
+ options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
+ ),
+ migrations.AlterModelOptions(
+ name='sidebar',
+ options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
+ ),
+ migrations.AlterModelOptions(
+ name='tag',
+ options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
+ ),
+ migrations.RemoveField(
+ model_name='article',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='article',
+ name='last_mod_time',
+ ),
+ migrations.RemoveField(
+ model_name='category',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='category',
+ name='last_mod_time',
+ ),
+ migrations.RemoveField(
+ model_name='links',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='sidebar',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='tag',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='tag',
+ name='last_mod_time',
+ ),
+ migrations.AddField(
+ model_name='article',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='article',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AddField(
+ model_name='links',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='sidebar',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='tag',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='tag',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='article_order',
+ field=models.IntegerField(default=0, verbose_name='order'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='author',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='body',
+ field=mdeditor.fields.MDTextField(verbose_name='body'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='category',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='comment_status',
+ field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='pub_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='show_toc',
+ field=models.BooleanField(default=False, verbose_name='show toc'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='status',
+ field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='tags',
+ field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='title',
+ field=models.CharField(max_length=200, unique=True, verbose_name='title'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='type',
+ field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='views',
+ field=models.PositiveIntegerField(default=0, verbose_name='views'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='article_comment_count',
+ field=models.IntegerField(default=5, verbose_name='article comment count'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='article_sub_length',
+ field=models.IntegerField(default=300, verbose_name='article sub length'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='google_adsense_codes',
+ field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='open_site_comment',
+ field=models.BooleanField(default=True, verbose_name='open site comment'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='show_google_adsense',
+ field=models.BooleanField(default=False, verbose_name='show adsense'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='sidebar_article_count',
+ field=models.IntegerField(default=10, verbose_name='sidebar article count'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='sidebar_comment_count',
+ field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='site_description',
+ field=models.TextField(default='', max_length=1000, verbose_name='site description'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='site_keywords',
+ field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='site_name',
+ field=models.CharField(default='', max_length=200, verbose_name='site name'),
+ ),
+ migrations.AlterField(
+ model_name='blogsettings',
+ name='site_seo_description',
+ field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='index',
+ field=models.IntegerField(default=0, verbose_name='index'),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='name',
+ field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='parent_category',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='is_enable',
+ field=models.BooleanField(default=True, verbose_name='is show'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='last_mod_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='link',
+ field=models.URLField(verbose_name='link'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='name',
+ field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='sequence',
+ field=models.IntegerField(unique=True, verbose_name='order'),
+ ),
+ migrations.AlterField(
+ model_name='links',
+ name='show_type',
+ field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'),
+ ),
+ migrations.AlterField(
+ model_name='sidebar',
+ name='content',
+ field=models.TextField(verbose_name='content'),
+ ),
+ migrations.AlterField(
+ model_name='sidebar',
+ name='is_enable',
+ field=models.BooleanField(default=True, verbose_name='is enable'),
+ ),
+ migrations.AlterField(
+ model_name='sidebar',
+ name='last_mod_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AlterField(
+ model_name='sidebar',
+ name='name',
+ field=models.CharField(max_length=100, verbose_name='title'),
+ ),
+ migrations.AlterField(
+ model_name='sidebar',
+ name='sequence',
+ field=models.IntegerField(unique=True, verbose_name='order'),
+ ),
+ migrations.AlterField(
+ model_name='tag',
+ name='name',
+ field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
+ ),
+ ]
diff --git a/blog/models.py b/blog/models.py
index 59e72c610..17f2fb894 100644
--- a/blog/models.py
+++ b/blog/models.py
@@ -17,17 +17,17 @@
class LinkShowType(models.TextChoices):
- I = ('i', '首页')
- L = ('l', '列表页')
- P = ('p', '文章页面')
- A = ('a', '全站')
- S = ('s', '友情链接页面')
+ I = ('i', _('index'))
+ L = ('l', _('list'))
+ P = ('p', _('post'))
+ A = ('a', _('all'))
+ S = ('s', _('slide'))
class BaseModel(models.Model):
id = models.AutoField(primary_key=True)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_modify_time = models.DateTimeField(_('modify time'), default=now)
def save(self, *args, **kwargs):
is_update_views = isinstance(
@@ -60,49 +60,49 @@ def get_absolute_url(self):
class Article(BaseModel):
"""文章"""
STATUS_CHOICES = (
- ('d', '草稿'),
- ('p', '发表'),
+ ('d', _('Draft')),
+ ('p', _('Published')),
)
COMMENT_STATUS = (
- ('o', '打开'),
- ('c', '关闭'),
+ ('o', _('Open')),
+ ('c', _('Close')),
)
TYPE = (
- ('a', '文章'),
- ('p', '页面'),
+ ('a', _('Article')),
+ ('p', _('Page')),
)
- title = models.CharField('标题', max_length=200, unique=True)
- body = MDTextField('正文')
+ title = models.CharField(_('title'), max_length=200, unique=True)
+ body = MDTextField(_('body'))
pub_time = models.DateTimeField(
- '发布时间', blank=False, null=False, default=now)
+ _('publish time'), blank=False, null=False, default=now)
status = models.CharField(
- '文章状态',
+ _('status'),
max_length=1,
choices=STATUS_CHOICES,
default='p')
comment_status = models.CharField(
- '评论状态',
+ _('comment status'),
max_length=1,
choices=COMMENT_STATUS,
default='o')
- type = models.CharField('类型', max_length=1, choices=TYPE, default='a')
- views = models.PositiveIntegerField('浏览量', default=0)
+ type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
+ views = models.PositiveIntegerField(_('views'), default=0)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
- verbose_name='作者',
+ verbose_name=_('author'),
blank=False,
null=False,
on_delete=models.CASCADE)
article_order = models.IntegerField(
- '排序,数字越大越靠前', blank=False, null=False, default=0)
- show_toc = models.BooleanField("是否显示toc目录", blank=False, null=False, default=False)
+ _('order'), blank=False, null=False, default=0)
+ show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False)
category = models.ForeignKey(
'Category',
- verbose_name='分类',
+ verbose_name=_('category'),
on_delete=models.CASCADE,
blank=False,
null=False)
- tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
+ tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True)
def body_to_string(self):
return self.body
@@ -112,16 +112,16 @@ def __str__(self):
class Meta:
ordering = ['-article_order', '-pub_time']
- verbose_name = "文章"
+ verbose_name = _('article')
verbose_name_plural = verbose_name
get_latest_by = 'id'
def get_absolute_url(self):
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
- 'year': self.created_time.year,
- 'month': self.created_time.month,
- 'day': self.created_time.day
+ 'year': self.creation_time.year,
+ 'month': self.creation_time.month,
+ 'day': self.creation_time.day
})
@cache_decorator(60 * 60 * 10)
@@ -168,19 +168,19 @@ def prev_article(self):
class Category(BaseModel):
"""文章分类"""
- name = models.CharField('分类名', max_length=30, unique=True)
+ name = models.CharField(_('category name'), max_length=30, unique=True)
parent_category = models.ForeignKey(
'self',
- verbose_name="父级分类",
+ verbose_name=_('parent category'),
blank=True,
null=True,
on_delete=models.CASCADE)
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
- index = models.IntegerField(default=0, verbose_name="权重排序-越大越靠前")
+ index = models.IntegerField(default=0, verbose_name=_('index'))
class Meta:
ordering = ['-index']
- verbose_name = "分类"
+ verbose_name = _('category')
verbose_name_plural = verbose_name
def get_absolute_url(self):
@@ -231,7 +231,7 @@ def parse(category):
class Tag(BaseModel):
"""文章标签"""
- name = models.CharField('标签名', max_length=30, unique=True)
+ name = models.CharField(_('tag name'), max_length=30, unique=True)
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
def __str__(self):
@@ -246,29 +246,29 @@ def get_article_count(self):
class Meta:
ordering = ['name']
- verbose_name = "标签"
+ verbose_name = _('tag')
verbose_name_plural = verbose_name
class Links(models.Model):
"""友情链接"""
- name = models.CharField('链接名称', max_length=30, unique=True)
- link = models.URLField('链接地址')
- sequence = models.IntegerField('排序', unique=True)
+ name = models.CharField(_('link name'), max_length=30, unique=True)
+ link = models.URLField(_('link'))
+ sequence = models.IntegerField(_('order'), unique=True)
is_enable = models.BooleanField(
- '是否显示', default=True, blank=False, null=False)
+ _('is show'), default=True, blank=False, null=False)
show_type = models.CharField(
- '显示类型',
+ _('show type'),
max_length=1,
choices=LinkShowType.choices,
default=LinkShowType.I)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_mod_time = models.DateTimeField(_('modify time'), default=now)
class Meta:
ordering = ['sequence']
- verbose_name = '友情链接'
+ verbose_name = _('link')
verbose_name_plural = verbose_name
def __str__(self):
@@ -277,16 +277,16 @@ def __str__(self):
class SideBar(models.Model):
"""侧边栏,可以展示一些html内容"""
- name = models.CharField('标题', max_length=100)
- content = models.TextField("内容")
- sequence = models.IntegerField('排序', unique=True)
- is_enable = models.BooleanField('是否启用', default=True)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ name = models.CharField(_('title'), max_length=100)
+ content = models.TextField(_('content'))
+ sequence = models.IntegerField(_('order'), unique=True)
+ is_enable = models.BooleanField(_('is enable'), default=True)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_mod_time = models.DateTimeField(_('modify time'), default=now)
class Meta:
ordering = ['sequence']
- verbose_name = '侧边栏'
+ verbose_name = _('sidebar')
verbose_name_plural = verbose_name
def __str__(self):
@@ -296,33 +296,33 @@ def __str__(self):
class BlogSettings(models.Model):
"""blog的配置"""
site_name = models.CharField(
- "网站名称",
+ _('site name'),
max_length=200,
null=False,
blank=False,
default='')
site_description = models.TextField(
- "网站描述",
+ _('site description'),
max_length=1000,
null=False,
blank=False,
default='')
site_seo_description = models.TextField(
- "网站SEO描述", max_length=1000, null=False, blank=False, default='')
+ _('site seo description'), max_length=1000, null=False, blank=False, default='')
site_keywords = models.TextField(
- "网站关键字",
+ _('site keywords'),
max_length=1000,
null=False,
blank=False,
default='')
- article_sub_length = models.IntegerField("文章摘要长度", default=300)
- sidebar_article_count = models.IntegerField("侧边栏文章数目", default=10)
- sidebar_comment_count = models.IntegerField("侧边栏评论数目", default=5)
- article_comment_count = models.IntegerField("文章页面默认显示评论数目", default=5)
- show_google_adsense = models.BooleanField('是否显示谷歌广告', default=False)
+ article_sub_length = models.IntegerField(_('article sub length'), default=300)
+ sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10)
+ sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5)
+ article_comment_count = models.IntegerField(_('article comment count'), default=5)
+ show_google_adsense = models.BooleanField(_('show adsense'), default=False)
google_adsense_codes = models.TextField(
- '广告内容', max_length=2000, null=True, blank=True, default='')
- open_site_comment = models.BooleanField('是否打开网站评论功能', default=True)
+ _('adsense code'), max_length=2000, null=True, blank=True, default='')
+ open_site_comment = models.BooleanField(_('open site comment'), default=True)
global_header = models.TextField("公共头部", null=True, blank=True, default='')
global_footer = models.TextField("公共尾部", null=True, blank=True, default='')
beian_code = models.CharField(
@@ -349,7 +349,7 @@ class BlogSettings(models.Model):
'评论是否需要审核', default=False, null=False)
class Meta:
- verbose_name = '网站配置'
+ verbose_name = _('Website configuration')
verbose_name_plural = verbose_name
def __str__(self):
@@ -357,7 +357,7 @@ def __str__(self):
def clean(self):
if BlogSettings.objects.exclude(id=self.id).count():
- raise ValidationError(_('只能有一个配置'))
+ raise ValidationError(_('There can only be one configuration'))
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
diff --git a/blog/static/blog/js/blog.js b/blog/static/blog/js/blog.js
index c8f8822d5..b783707bd 100644
--- a/blog/static/blog/js/blog.js
+++ b/blog/static/blog/js/blog.js
@@ -30,42 +30,51 @@ $(document).ready(function () {
});
-
/** 侧边栏回到顶部 */
var rocket = $('#rocket');
$(window).on('scroll', debounce(slideTopSet, 300));
function debounce(func, wait) {
- var timeout;
- return function() {
- clearTimeout(timeout);
- timeout = setTimeout(func, wait);
- };
-};
+ var timeout;
+ return function () {
+ clearTimeout(timeout);
+ timeout = setTimeout(func, wait);
+ };
+}
+
function slideTopSet() {
- var top = $(document).scrollTop();
+ var top = $(document).scrollTop();
- if (top > 200) {
- rocket.addClass('show');
- } else {
- rocket.removeClass('show');
- }
+ if (top > 200) {
+ rocket.addClass('show');
+ } else {
+ rocket.removeClass('show');
+ }
}
-$(document).on('click', '#rocket', function(event) {
- rocket.addClass('move');
- $('body, html').animate({
- scrollTop: 0
- }, 800);
+
+$(document).on('click', '#rocket', function (event) {
+ rocket.addClass('move');
+ $('body, html').animate({
+ scrollTop: 0
+ }, 800);
});
-$(document).on('animationEnd', function() {
- setTimeout(function() {
- rocket.removeClass('move');
- }, 400);
+$(document).on('animationEnd', function () {
+ setTimeout(function () {
+ rocket.removeClass('move');
+ }, 400);
});
-$(document).on('webkitAnimationEnd', function() {
- setTimeout(function() {
- rocket.removeClass('move');
- }, 400);
+$(document).on('webkitAnimationEnd', function () {
+ setTimeout(function () {
+ rocket.removeClass('move');
+ }, 400);
});
+
+// $(document).ready(function () {
+// var form = $('#i18n-form');
+// var selector = $('.i18n-select');
+// selector.on('change', function () {
+// form.submit();
+// });
+// });
\ No newline at end of file
diff --git a/blog/static/blog/js/nprogress.js b/blog/static/blog/js/nprogress.js
index beb9d2cb9..d29c2aac7 100644
--- a/blog/static/blog/js/nprogress.js
+++ b/blog/static/blog/js/nprogress.js
@@ -161,7 +161,7 @@
if (!n) {
return NProgress.start();
} else if(n > 1) {
- return;
+
} else {
if (typeof amount !== 'number') {
if (n >= 0 && n < 0.2) { amount = 0.1; }
diff --git a/blog/templatetags/blog_tags.py b/blog/templatetags/blog_tags.py
index db9c3c9ed..110b22b9e 100644
--- a/blog/templatetags/blog_tags.py
+++ b/blog/templatetags/blog_tags.py
@@ -146,7 +146,7 @@ def load_sidebar(user, linktype):
is_enable=True).order_by('sequence')
most_read_articles = Article.objects.filter(status='p').order_by(
'-views')[:blogsetting.sidebar_article_count]
- dates = Article.objects.datetimes('created_time', 'month', order='DESC')
+ dates = Article.objects.datetimes('creation_time', 'month', order='DESC')
links = Links.objects.filter(is_enable=True).filter(
Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A))
commment_list = Comment.objects.filter(is_enable=True).order_by(
diff --git a/blog/tests.py b/blog/tests.py
index 7be7b473b..f6bfac058 100644
--- a/blog/tests.py
+++ b/blog/tests.py
@@ -46,7 +46,7 @@ def test_validate_article(self):
category = Category()
category.name = "category"
- category.created_time = timezone.now()
+ category.creation_time = timezone.now()
category.last_mod_time = timezone.now()
category.save()
@@ -105,19 +105,19 @@ def test_validate_article(self):
response = self.client.get(reverse('blog:archives'))
self.assertEqual(response.status_code, 200)
- p = Paginator(Article.objects.all(), 2)
- self.__check_pagination__(p, '', '')
+ p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
+ self.check_pagination(p, '', '')
- p = Paginator(Article.objects.filter(tags=tag), 2)
- self.__check_pagination__(p, '分类标签归档', tag.slug)
+ p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
+ self.check_pagination(p, '分类标签归档', tag.slug)
p = Paginator(
Article.objects.filter(
- author__username='liangliangyy'), 2)
- self.__check_pagination__(p, '作者文章归档', 'liangliangyy')
+ author__username='liangliangyy'), settings.PAGINATE_BY)
+ self.check_pagination(p, '作者文章归档', 'liangliangyy')
- p = Paginator(Article.objects.filter(category=category), 2)
- self.__check_pagination__(p, '分类目录归档', category.slug)
+ p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
+ self.check_pagination(p, '分类目录归档', category.slug)
f = BlogSearchForm()
f.search()
@@ -148,20 +148,16 @@ def test_validate_article(self):
self.client.get('/admin/admin/logentry/')
self.client.get('/admin/admin/logentry/1/change/')
- def __check_pagination__(self, p, type, value):
- s = load_pagination_info(p.page(1), type, value)
- self.assertIsNotNone(s)
- response = self.client.get(s['previous_url'])
- self.assertEqual(response.status_code, 200)
- response = self.client.get(s['next_url'])
- self.assertEqual(response.status_code, 200)
-
- s = load_pagination_info(p.page(2), type, value)
- self.assertIsNotNone(s)
- response = self.client.get(s['previous_url'])
- self.assertEqual(response.status_code, 200)
- response = self.client.get(s['next_url'])
- self.assertEqual(response.status_code, 200)
+ def check_pagination(self, p, type, value):
+ for page in range(1, p.num_pages + 1):
+ s = load_pagination_info(p.page(page), type, value)
+ self.assertIsNotNone(s)
+ if s['previous_url']:
+ response = self.client.get(s['previous_url'])
+ self.assertEqual(response.status_code, 200)
+ if s['next_url']:
+ response = self.client.get(s['next_url'])
+ self.assertEqual(response.status_code, 200)
def test_image(self):
import requests
diff --git a/blog/urls.py b/blog/urls.py
index baec47ea6..adf270363 100644
--- a/blog/urls.py
+++ b/blog/urls.py
@@ -55,4 +55,8 @@
r'upload',
views.fileupload,
name='upload'),
+ path(
+ r'clean',
+ views.clean_cache_view,
+ name='clean'),
]
diff --git a/blog/views.py b/blog/views.py
index 18bd0800a..4af92428e 100644
--- a/blog/views.py
+++ b/blog/views.py
@@ -9,6 +9,7 @@
from django.shortcuts import render
from django.templatetags.static import static
from django.utils import timezone
+from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
@@ -344,7 +345,7 @@ def page_not_found_view(
url = request.get_full_path()
return render(request,
template_name,
- {'message': '哎呀,您访问的地址 ' + url + ' 是一个未知的地方。请点击首页看看别的?',
+ {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
'statuscode': '404'},
status=404)
@@ -352,7 +353,7 @@ def page_not_found_view(
def server_error_view(request, template_name='blog/error_page.html'):
return render(request,
template_name,
- {'message': '哎呀,出错了,我已经收集到了错误信息,之后会抓紧抢修,请点击首页看看别的?',
+ {'message': _('Sorry, the server is busy, please click the home page to see other?'),
'statuscode': '500'},
status=500)
@@ -365,4 +366,10 @@ def permission_denied_view(
logger.error(exception)
return render(
request, template_name, {
- 'message': '哎呀,您没有权限访问此页面,请点击首页看看别的?', 'statuscode': '403'}, status=403)
+ 'message': _('Sorry, you do not have permission to access this page?'),
+ 'statuscode': '403'}, status=403)
+
+
+def clean_cache_view(request):
+ cache.clear()
+ return HttpResponse('ok')
diff --git a/comments/admin.py b/comments/admin.py
index 9252174d9..5622781f1 100644
--- a/comments/admin.py
+++ b/comments/admin.py
@@ -1,7 +1,7 @@
from django.contrib import admin
-# Register your models here.
from django.urls import reverse
from django.utils.html import format_html
+from django.utils.translation import gettext_lazy as _
def disable_commentstatus(modeladmin, request, queryset):
@@ -12,8 +12,8 @@ def enable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=True)
-disable_commentstatus.short_description = '禁用评论'
-enable_commentstatus.short_description = '启用评论'
+disable_commentstatus.short_description = _('Disable comments')
+enable_commentstatus.short_description = _('Enable comments')
class CommentAdmin(admin.ModelAdmin):
@@ -24,10 +24,10 @@ class CommentAdmin(admin.ModelAdmin):
'link_to_userinfo',
'link_to_article',
'is_enable',
- 'created_time')
+ 'creation_time')
list_display_links = ('id', 'body', 'is_enable')
list_filter = ('is_enable', 'author', 'article',)
- exclude = ('created_time', 'last_mod_time')
+ exclude = ('creation_time', 'last_modify_time')
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
@@ -43,5 +43,5 @@ def link_to_article(self, obj):
return format_html(
u'%s' % (link, obj.article.title))
- link_to_userinfo.short_description = '用户'
- link_to_article.short_description = '文章'
+ link_to_userinfo.short_description = _('User')
+ link_to_article.short_description = _('Article')
diff --git a/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
new file mode 100644
index 000000000..a1ca9708a
--- /dev/null
+++ b/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
@@ -0,0 +1,60 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:13
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('blog', '0005_alter_article_options_alter_category_options_and_more'),
+ ('comments', '0002_alter_comment_is_enable'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='comment',
+ options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'},
+ ),
+ migrations.RemoveField(
+ model_name='comment',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='comment',
+ name='last_mod_time',
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='comment',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='article',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='author',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='is_enable',
+ field=models.BooleanField(default=False, verbose_name='enable'),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='parent_comment',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'),
+ ),
+ ]
diff --git a/comments/models.py b/comments/models.py
index 27c67eff2..7c3bbc8da 100644
--- a/comments/models.py
+++ b/comments/models.py
@@ -1,6 +1,7 @@
from django.conf import settings
from django.db import models
from django.utils.timezone import now
+from django.utils.translation import gettext_lazy as _
from blog.models import Article
@@ -9,28 +10,28 @@
class Comment(models.Model):
body = models.TextField('正文', max_length=300)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_modify_time = models.DateTimeField(_('last modify time'), default=now)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
- verbose_name='作者',
+ verbose_name=_('author'),
on_delete=models.CASCADE)
article = models.ForeignKey(
Article,
- verbose_name='文章',
+ verbose_name=_('article'),
on_delete=models.CASCADE)
parent_comment = models.ForeignKey(
'self',
- verbose_name="上级评论",
+ verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
- is_enable = models.BooleanField(
- '是否显示', default=False, blank=False, null=False)
+ is_enable = models.BooleanField(_('enable'),
+ default=False, blank=False, null=False)
class Meta:
ordering = ['-id']
- verbose_name = "评论"
+ verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id'
diff --git a/comments/tests.py b/comments/tests.py
index d503d0ad6..2a7f55f1f 100644
--- a/comments/tests.py
+++ b/comments/tests.py
@@ -1,6 +1,5 @@
-from django.test import Client, RequestFactory, TestCase
+from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
-from django.utils import timezone
from accounts.models import BlogUser
from blog.models import Category, Article
@@ -11,7 +10,7 @@
# Create your tests here.
-class CommentsTest(TestCase):
+class CommentsTest(TransactionTestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
@@ -20,6 +19,11 @@ def setUp(self):
value.comment_need_review = True
value.save()
+ self.user = BlogUser.objects.create_superuser(
+ email="liangliangyy1@gmail.com",
+ username="liangliangyy1",
+ password="liangliangyy1")
+
def update_article_comment_status(self, article):
comments = article.comment_set.all()
for comment in comments:
@@ -27,23 +31,16 @@ def update_article_comment_status(self, article):
comment.save()
def test_validate_comment(self):
- user = BlogUser.objects.create_superuser(
- email="liangliangyy1@gmail.com",
- username="liangliangyy1",
- password="liangliangyy1")
-
self.client.login(username='liangliangyy1', password='liangliangyy1')
category = Category()
category.name = "categoryccc"
- category.created_time = timezone.now()
- category.last_mod_time = timezone.now()
category.save()
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
- article.author = user
+ article.author = self.user
article.category = category
article.type = 'a'
article.status = 'p'
diff --git a/comments/utils.py b/comments/utils.py
index 0380f0823..f01dba7ef 100644
--- a/comments/utils.py
+++ b/comments/utils.py
@@ -1,5 +1,7 @@
import logging
+from django.utils.translation import gettext_lazy as _
+
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
@@ -8,29 +10,28 @@
def send_comment_email(comment):
site = get_current_site().domain
- subject = '感谢您发表的评论'
- article_url = "https://{site}{path}".format(
- site=site, path=comment.article.get_absolute_url())
- html_content = """
-
非常感谢您在本站发表评论
- 您可以访问
- %s
- 来查看您的评论,
- 再次感谢您!
-
- 如果上面链接无法打开,请将此链接复制至浏览器。
- %s
- """ % (article_url, comment.article.title, article_url)
+ subject = _('Thanks for your comment')
+ article_url = f"https://{site}{comment.article.get_absolute_url()}"
+ html_content = _("""Thank you very much for your comments on this site
+ You can visit %(article_title)s
+ to review your comments,
+ Thank you again!
+
+ If the link above cannot be opened, please copy this link to your browser.
+ %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
tomail = comment.author.email
send_email([tomail], subject, html_content)
try:
if comment.parent_comment:
- html_content = """
- 您在 %s 的评论
%s
收到回复啦.快去看看吧
-
- 如果上面链接无法打开,请将此链接复制至浏览器。
- %s
- """ % (article_url, comment.article.title, comment.parent_comment.body, article_url)
+ html_content = _("""Your comment on %(article_title)s
has
+ received a reply.
%(comment_body)s
+
+ go check it out!
+
+ If the link above cannot be opened, please copy this link to your browser.
+ %(article_url)s
+ """) % {'article_url': article_url, 'article_title': comment.article.title,
+ 'comment_body': comment.parent_comment.body}
tomail = comment.parent_comment.author.email
send_email([tomail], subject, html_content)
except Exception as e:
diff --git a/comments/views.py b/comments/views.py
index 8ad0fe5d7..ad9b2b94c 100644
--- a/comments/views.py
+++ b/comments/views.py
@@ -6,6 +6,7 @@
from django.views.decorators.csrf import csrf_protect
from django.views.generic.edit import FormView
+from accounts.models import BlogUser
from blog.models import Article
from .forms import CommentForm
from .models import Comment
@@ -37,7 +38,7 @@ def form_invalid(self, form):
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
user = self.request.user
-
+ author = BlogUser.objects.get(pk=user.pk)
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
@@ -49,7 +50,7 @@ def form_valid(self, form):
settings = get_blog_setting()
if not settings.comment_need_review:
comment.is_enable = True
- comment.author = user
+ comment.author = author
if form.cleaned_data['parent_comment_id']:
parent_comment = Comment.objects.get(
diff --git a/djangoblog/logentryadmin.py b/djangoblog/logentryadmin.py
index 9623324a3..2f6a53533 100644
--- a/djangoblog/logentryadmin.py
+++ b/djangoblog/logentryadmin.py
@@ -1,45 +1,14 @@
from django.contrib import admin
-from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
+from django.contrib.admin.models import DELETION
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse, NoReverseMatch
from django.utils.encoding import force_str
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.utils.translation import pgettext_lazy, gettext_lazy as _
-
-action_names = {
- ADDITION: pgettext_lazy('logentry_admin:action_type', 'Addition'),
- DELETION: pgettext_lazy('logentry_admin:action_type', 'Deletion'),
- CHANGE: pgettext_lazy('logentry_admin:action_type', 'Change'),
-}
+from django.utils.translation import gettext_lazy as _
class LogEntryAdmin(admin.ModelAdmin):
- date_hierarchy = 'action_time'
-
- readonly_fields = ([f.name for f in LogEntry._meta.fields] +
- ['object_link', 'action_description', 'user_link',
- 'get_change_message'])
-
- fieldsets = (
- (_('Metadata'), {
- 'fields': (
- 'action_time',
- 'user_link',
- 'action_description',
- 'object_link',
- )
- }),
- (_('Details'), {
- 'fields': (
- 'get_change_message',
- 'content_type',
- 'object_id',
- 'object_repr',
- )
- }),
- )
-
list_filter = [
'content_type'
]
@@ -58,7 +27,6 @@ class LogEntryAdmin(admin.ModelAdmin):
'user_link',
'content_type',
'object_link',
- 'action_description',
'get_change_message',
]
@@ -67,9 +35,9 @@ def has_add_permission(self, request):
def has_change_permission(self, request, obj=None):
return (
- request.user.is_superuser or
- request.user.has_perm('admin.change_logentry')
- ) and request.method != 'POST'
+ request.user.is_superuser or
+ request.user.has_perm('admin.change_logentry')
+ ) and request.method != 'POST'
def has_delete_permission(self, request, obj=None):
return False
@@ -121,13 +89,3 @@ def get_actions(self, request):
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
-
- def action_description(self, obj):
- return action_names[obj.action_flag]
-
- action_description.short_description = _('action')
-
- def get_change_message(self, obj):
- return obj.get_change_message()
-
- get_change_message.short_description = _('change message')
diff --git a/djangoblog/settings.py b/djangoblog/settings.py
index 5c519757c..54b00be23 100644
--- a/djangoblog/settings.py
+++ b/djangoblog/settings.py
@@ -12,6 +12,8 @@
import os
import sys
+from django.utils.translation import gettext_lazy as _
+
def env_to_bool(env, default):
str_val = os.environ.get(env)
@@ -62,8 +64,10 @@ def env_to_bool(env, default):
]
MIDDLEWARE = [
+
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -132,8 +136,14 @@ def env_to_bool(env, default):
},
]
-# Internationalization
-# https://docs.djangoproject.com/en/1.10/topics/i18n/
+LANGUAGES = (
+ ('en', _('English')),
+ ('zh-hans', _('Simplified Chinese')),
+ ('zh-hant', _('Traditional Chinese')),
+)
+LOCALE_PATHS = (
+ os.path.join(BASE_DIR, 'locale'),
+)
LANGUAGE_CODE = 'zh-hans'
diff --git a/djangoblog/sitemap.py b/djangoblog/sitemap.py
index 151492e5d..8b7d44608 100644
--- a/djangoblog/sitemap.py
+++ b/djangoblog/sitemap.py
@@ -23,7 +23,7 @@ def items(self):
return Article.objects.filter(status='p')
def lastmod(self, obj):
- return obj.last_mod_time
+ return obj.last_modify_time
class CategorySiteMap(Sitemap):
@@ -34,7 +34,7 @@ def items(self):
return Category.objects.all()
def lastmod(self, obj):
- return obj.last_mod_time
+ return obj.last_modify_time
class TagSiteMap(Sitemap):
@@ -45,7 +45,7 @@ def items(self):
return Tag.objects.all()
def lastmod(self, obj):
- return obj.last_mod_time
+ return obj.last_modify_time
class UserSiteMap(Sitemap):
diff --git a/djangoblog/urls.py b/djangoblog/urls.py
index a834684bb..4aae58a68 100644
--- a/djangoblog/urls.py
+++ b/djangoblog/urls.py
@@ -14,9 +14,10 @@
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf import settings
+from django.conf.urls.i18n import i18n_patterns
from django.conf.urls.static import static
from django.contrib.sitemaps.views import sitemap
-from django.urls import include
+from django.urls import path, include
from django.urls import re_path
from haystack.views import search_view_factory
@@ -38,22 +39,26 @@
handler404 = 'blog.views.page_not_found_view'
handler500 = 'blog.views.server_error_view'
handle403 = 'blog.views.permission_denied_view'
+
urlpatterns = [
- re_path(r'^admin/', admin_site.urls),
- re_path(r'', include('blog.urls', namespace='blog')),
- re_path(r'mdeditor/', include('mdeditor.urls')),
- re_path(r'', include('comments.urls', namespace='comment')),
- re_path(r'', include('accounts.urls', namespace='account')),
- re_path(r'', include('oauth.urls', namespace='oauth')),
- re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
- name='django.contrib.sitemaps.views.sitemap'),
- re_path(r'^feed/$', DjangoBlogFeed()),
- re_path(r'^rss/$', DjangoBlogFeed()),
- re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
- name='search'),
- re_path(r'', include('servermanager.urls', namespace='servermanager')),
- re_path(r'', include('owntracks.urls', namespace='owntracks'))
- ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+ path('i18n/', include('django.conf.urls.i18n')),
+]
+urlpatterns += i18n_patterns(
+ re_path(r'^admin/', admin_site.urls),
+ re_path(r'', include('blog.urls', namespace='blog')),
+ re_path(r'mdeditor/', include('mdeditor.urls')),
+ re_path(r'', include('comments.urls', namespace='comment')),
+ re_path(r'', include('accounts.urls', namespace='account')),
+ re_path(r'', include('oauth.urls', namespace='oauth')),
+ re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
+ name='django.contrib.sitemaps.views.sitemap'),
+ re_path(r'^feed/$', DjangoBlogFeed()),
+ re_path(r'^rss/$', DjangoBlogFeed()),
+ re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
+ name='search'),
+ re_path(r'', include('servermanager.urls', namespace='servermanager')),
+ re_path(r'', include('owntracks.urls', namespace='owntracks'))
+ , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 000000000..c80b30ac7
--- /dev/null
+++ b/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,685 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-09-13 16:02+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: .\accounts\admin.py:12
+msgid "password"
+msgstr "password"
+
+#: .\accounts\admin.py:13
+msgid "Enter password again"
+msgstr "Enter password again"
+
+#: .\accounts\admin.py:24 .\accounts\forms.py:89
+msgid "passwords do not match"
+msgstr "passwords do not match"
+
+#: .\accounts\forms.py:36
+msgid "email already exists"
+msgstr "email already exists"
+
+#: .\accounts\forms.py:46 .\accounts\forms.py:50
+msgid "New password"
+msgstr "New password"
+
+#: .\accounts\forms.py:60
+msgid "Confirm password"
+msgstr "Confirm password"
+
+#: .\accounts\forms.py:70 .\accounts\forms.py:116
+msgid "Email"
+msgstr "Email"
+
+#: .\accounts\forms.py:76 .\accounts\forms.py:80
+msgid "Code"
+msgstr "Code"
+
+#: .\accounts\forms.py:100 .\accounts\tests.py:194
+msgid "email does not exist"
+msgstr "email does not exist"
+
+#: .\accounts\models.py:12 .\oauth\models.py:17
+msgid "nick name"
+msgstr "nick name"
+
+#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266
+#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23
+#: .\oauth\models.py:53
+msgid "creation time"
+msgstr "creation time"
+
+#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24
+#: .\oauth\models.py:54
+msgid "last modify time"
+msgstr "last modify time"
+
+#: .\accounts\models.py:15
+msgid "create source"
+msgstr "create source"
+
+#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81
+msgid "user"
+msgstr "user"
+
+#: .\accounts\tests.py:216 .\accounts\utils.py:39
+msgid "Verification code error"
+msgstr "Verification code error"
+
+#: .\accounts\utils.py:13
+msgid "Verify Email"
+msgstr "Verify Email"
+
+#: .\accounts\utils.py:21
+#, python-format
+msgid ""
+"You are resetting the password, the verification code is:%(code)s, valid "
+"within 5 minutes, please keep it properly"
+msgstr ""
+"You are resetting the password, the verification code is:%(code)s, valid "
+"within 5 minutes, please keep it properly"
+
+#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17
+#: .\oauth\models.py:12
+msgid "author"
+msgstr "author"
+
+#: .\blog\admin.py:53
+msgid "Publish selected articles"
+msgstr "Publish selected articles"
+
+#: .\blog\admin.py:54
+msgid "Draft selected articles"
+msgstr "Draft selected articles"
+
+#: .\blog\admin.py:55
+msgid "Close article comments"
+msgstr "Close article comments"
+
+#: .\blog\admin.py:56
+msgid "Open article comments"
+msgstr "Open article comments"
+
+#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183
+#: .\templates\blog\tags\sidebar.html:40
+msgid "category"
+msgstr "category"
+
+#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8
+msgid "index"
+msgstr "index"
+
+#: .\blog\models.py:21
+msgid "list"
+msgstr "list"
+
+#: .\blog\models.py:22
+msgid "post"
+msgstr "post"
+
+#: .\blog\models.py:23
+msgid "all"
+msgstr "all"
+
+#: .\blog\models.py:24
+msgid "slide"
+msgstr "slide"
+
+#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285
+msgid "modify time"
+msgstr "modify time"
+
+#: .\blog\models.py:63
+msgid "Draft"
+msgstr "Draft"
+
+#: .\blog\models.py:64
+msgid "Published"
+msgstr "Published"
+
+#: .\blog\models.py:67
+msgid "Open"
+msgstr "Open"
+
+#: .\blog\models.py:68
+msgid "Close"
+msgstr "Close"
+
+#: .\blog\models.py:71 .\comments\admin.py:47
+msgid "Article"
+msgstr "Article"
+
+#: .\blog\models.py:72
+msgid "Page"
+msgstr "Page"
+
+#: .\blog\models.py:74 .\blog\models.py:280
+msgid "title"
+msgstr "title"
+
+#: .\blog\models.py:75
+msgid "body"
+msgstr "body"
+
+#: .\blog\models.py:77
+msgid "publish time"
+msgstr "publish time"
+
+#: .\blog\models.py:79
+msgid "status"
+msgstr "status"
+
+#: .\blog\models.py:84
+msgid "comment status"
+msgstr "comment status"
+
+#: .\blog\models.py:88 .\oauth\models.py:43
+msgid "type"
+msgstr "type"
+
+#: .\blog\models.py:89
+msgid "views"
+msgstr "views"
+
+#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282
+msgid "order"
+msgstr "order"
+
+#: .\blog\models.py:98
+msgid "show toc"
+msgstr "show toc"
+
+#: .\blog\models.py:105 .\blog\models.py:249
+msgid "tag"
+msgstr "tag"
+
+#: .\blog\models.py:115 .\comments\models.py:21
+msgid "article"
+msgstr "article"
+
+#: .\blog\models.py:171
+msgid "category name"
+msgstr "category name"
+
+#: .\blog\models.py:174
+msgid "parent category"
+msgstr "parent category"
+
+#: .\blog\models.py:234
+msgid "tag name"
+msgstr "tag name"
+
+#: .\blog\models.py:256
+msgid "link name"
+msgstr "link name"
+
+#: .\blog\models.py:257 .\blog\models.py:271
+msgid "link"
+msgstr "link"
+
+#: .\blog\models.py:260
+msgid "is show"
+msgstr "is show"
+
+#: .\blog\models.py:262
+msgid "show type"
+msgstr "show type"
+
+#: .\blog\models.py:281
+msgid "content"
+msgstr "content"
+
+#: .\blog\models.py:283 .\oauth\models.py:52
+msgid "is enable"
+msgstr "is enable"
+
+#: .\blog\models.py:289
+msgid "sidebar"
+msgstr "sidebar"
+
+#: .\blog\models.py:299
+msgid "site name"
+msgstr "site name"
+
+#: .\blog\models.py:305
+msgid "site description"
+msgstr "site description"
+
+#: .\blog\models.py:311
+msgid "site seo description"
+msgstr "site seo description"
+
+#: .\blog\models.py:313
+msgid "site keywords"
+msgstr "site keywords"
+
+#: .\blog\models.py:318
+msgid "article sub length"
+msgstr "article sub length"
+
+#: .\blog\models.py:319
+msgid "sidebar article count"
+msgstr "sidebar article count"
+
+#: .\blog\models.py:320
+msgid "sidebar comment count"
+msgstr "sidebar comment count"
+
+#: .\blog\models.py:321
+msgid "article comment count"
+msgstr "article comment count"
+
+#: .\blog\models.py:322
+msgid "show adsense"
+msgstr "show adsense"
+
+#: .\blog\models.py:324
+msgid "adsense code"
+msgstr "adsense code"
+
+#: .\blog\models.py:325
+msgid "open site comment"
+msgstr "open site comment"
+
+#: .\blog\models.py:352
+msgid "Website configuration"
+msgstr "Website configuration"
+
+#: .\blog\models.py:360
+msgid "There can only be one configuration"
+msgstr "There can only be one configuration"
+
+#: .\blog\views.py:348
+msgid ""
+"Sorry, the page you requested is not found, please click the home page to "
+"see other?"
+msgstr ""
+"Sorry, the page you requested is not found, please click the home page to "
+"see other?"
+
+#: .\blog\views.py:356
+msgid "Sorry, the server is busy, please click the home page to see other?"
+msgstr "Sorry, the server is busy, please click the home page to see other?"
+
+#: .\blog\views.py:369
+msgid "Sorry, you do not have permission to access this page?"
+msgstr "Sorry, you do not have permission to access this page?"
+
+#: .\comments\admin.py:15
+msgid "Disable comments"
+msgstr "Disable comments"
+
+#: .\comments\admin.py:16
+msgid "Enable comments"
+msgstr "Enable comments"
+
+#: .\comments\admin.py:46
+msgid "User"
+msgstr "User"
+
+#: .\comments\models.py:25
+msgid "parent comment"
+msgstr "parent comment"
+
+#: .\comments\models.py:29
+msgid "enable"
+msgstr "enable"
+
+#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30
+msgid "comment"
+msgstr "comment"
+
+#: .\comments\utils.py:13
+msgid "Thanks for your comment"
+msgstr "Thanks for your comment"
+
+#: .\comments\utils.py:15
+#, python-format
+msgid ""
+"Thank you very much for your comments on this site
\n"
+" You can visit %(article_title)s\n"
+" to review your comments,\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s"
+msgstr ""
+"Thank you very much for your comments on this site
\n"
+" You can visit %(article_title)s\n"
+" to review your comments,\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s"
+
+#: .\comments\utils.py:26
+#, python-format
+msgid ""
+"Your comment on "
+"%(article_title)s
has \n"
+" received a reply.
%(comment_body)s\n"
+"
\n"
+" go check it out!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s\n"
+" "
+msgstr ""
+"Your comment on "
+"%(article_title)s
has \n"
+" received a reply.
%(comment_body)s\n"
+"
\n"
+" go check it out!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s\n"
+" "
+
+#: .\djangoblog\logentryadmin.py:63
+msgid "object"
+msgstr "object"
+
+#: .\djangoblog\settings.py:140
+msgid "English"
+msgstr "English"
+
+#: .\djangoblog\settings.py:141
+msgid "Simplified Chinese"
+msgstr "Simplified Chinese"
+
+#: .\djangoblog\settings.py:142
+msgid "Traditional Chinese"
+msgstr "Traditional Chinese"
+
+#: .\oauth\models.py:30
+msgid "oauth user"
+msgstr "oauth user"
+
+#: .\oauth\models.py:37
+msgid "weibo"
+msgstr "weibo"
+
+#: .\oauth\models.py:38
+msgid "google"
+msgstr "google"
+
+#: .\oauth\models.py:48
+msgid "callback url"
+msgstr "callback url"
+
+#: .\oauth\models.py:59
+msgid "already exists"
+msgstr "already exists"
+
+#: .\oauth\views.py:154
+#, python-format
+msgid ""
+"\n"
+" Congratulations, you have successfully bound your email address. You "
+"can use\n"
+" %(oauthuser_type)s to directly log in to this website without a "
+"password.
\n"
+" You are welcome to continue to follow this site, the address is\n"
+" %(site)s\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link to your "
+"browser.\n"
+" %(site)s\n"
+" "
+msgstr ""
+"\n"
+" Congratulations, you have successfully bound your email address. You "
+"can use\n"
+" %(oauthuser_type)s to directly log in to this website without a "
+"password.
\n"
+" You are welcome to continue to follow this site, the address is\n"
+" %(site)s\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link to your "
+"browser.\n"
+" %(site)s\n"
+" "
+
+#: .\oauth\views.py:165
+msgid "Congratulations on your successful binding!"
+msgstr "Congratulations on your successful binding!"
+
+#: .\oauth\views.py:217
+#, python-format
+msgid ""
+"\n"
+" Please click the link below to bind your email
\n"
+"\n"
+" %(url)s\n"
+"\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link "
+"to your browser.\n"
+"
\n"
+" %(url)s\n"
+" "
+msgstr ""
+"\n"
+" Please click the link below to bind your email
\n"
+"\n"
+" %(url)s\n"
+"\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link "
+"to your browser.\n"
+"
\n"
+" %(url)s\n"
+" "
+
+#: .\oauth\views.py:228 .\oauth\views.py:240
+msgid "Bind your email"
+msgstr "Bind your email"
+
+#: .\oauth\views.py:242
+msgid ""
+"Congratulations, the binding is just one step away. Please log in to your "
+"email to check the email to complete the binding. Thank you."
+msgstr ""
+"Congratulations, the binding is just one step away. Please log in to your "
+"email to check the email to complete the binding. Thank you."
+
+#: .\oauth\views.py:245
+msgid "Binding successful"
+msgstr "Binding successful"
+
+#: .\oauth\views.py:247
+#, python-format
+msgid ""
+"Congratulations, you have successfully bound your email address. You can use "
+"%(oauthuser_type)s to directly log in to this website without a password. "
+"You are welcome to continue to follow this site."
+msgstr ""
+"Congratulations, you have successfully bound your email address. You can use "
+"%(oauthuser_type)s to directly log in to this website without a password. "
+"You are welcome to continue to follow this site."
+
+#: .\templates\account\forget_password.html:7
+msgid "forget the password"
+msgstr "forget the password"
+
+#: .\templates\account\forget_password.html:18
+msgid "get verification code"
+msgstr "get verification code"
+
+#: .\templates\account\forget_password.html:19
+msgid "submit"
+msgstr "submit"
+
+#: .\templates\account\login.html:36
+msgid "Create Account"
+msgstr "Create Account"
+
+#: .\templates\account\login.html:42
+#, fuzzy
+#| msgid "forget the password"
+msgid "Forget Password"
+msgstr "forget the password"
+
+#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126
+msgid "login"
+msgstr "login"
+
+#: .\templates\account\result.html:22
+msgid "back to the homepage"
+msgstr "back to the homepage"
+
+#: .\templates\blog\article_archives.html:7
+#: .\templates\blog\article_archives.html:24
+msgid "article archive"
+msgstr "article archive"
+
+#: .\templates\blog\article_archives.html:32
+msgid "year"
+msgstr "year"
+
+#: .\templates\blog\article_archives.html:36
+msgid "month"
+msgstr "month"
+
+#: .\templates\blog\tags\article_info.html:12
+msgid "pin to top"
+msgstr "pin to top"
+
+#: .\templates\blog\tags\article_info.html:28
+msgid "comments"
+msgstr "comments"
+
+#: .\templates\blog\tags\article_info.html:58
+msgid "toc"
+msgstr "toc"
+
+#: .\templates\blog\tags\article_meta_info.html:6
+msgid "posted in"
+msgstr "posted in"
+
+#: .\templates\blog\tags\article_meta_info.html:14
+msgid "and tagged"
+msgstr "and tagged"
+
+#: .\templates\blog\tags\article_meta_info.html:25
+msgid "by "
+msgstr "by"
+
+#: .\templates\blog\tags\article_meta_info.html:29
+#, python-format
+msgid ""
+"\n"
+" title=\"View all articles published by "
+"%(article.author.username)s\"\n"
+" "
+msgstr ""
+"\n"
+" title=\"View all articles published by "
+"%(article.author.username)s\"\n"
+" "
+
+#: .\templates\blog\tags\article_meta_info.html:44
+msgid "on"
+msgstr "on"
+
+#: .\templates\blog\tags\article_meta_info.html:54
+msgid "edit"
+msgstr "edit"
+
+#: .\templates\blog\tags\article_pagination.html:4
+msgid "article navigation"
+msgstr "article navigation"
+
+#: .\templates\blog\tags\article_pagination.html:9
+msgid "earlier articles"
+msgstr "earlier articles"
+
+#: .\templates\blog\tags\article_pagination.html:12
+msgid "newer articles"
+msgstr "newer articles"
+
+#: .\templates\blog\tags\article_tag_list.html:5
+msgid "tags"
+msgstr "tags"
+
+#: .\templates\blog\tags\sidebar.html:7
+msgid "search"
+msgstr "search"
+
+#: .\templates\blog\tags\sidebar.html:50
+msgid "recent comments"
+msgstr "recent comments"
+
+#: .\templates\blog\tags\sidebar.html:57
+msgid "published on"
+msgstr "published on"
+
+#: .\templates\blog\tags\sidebar.html:65
+msgid "recent articles"
+msgstr "recent articles"
+
+#: .\templates\blog\tags\sidebar.html:77
+msgid "bookmark"
+msgstr "bookmark"
+
+#: .\templates\blog\tags\sidebar.html:96
+msgid "Tag Cloud"
+msgstr "Tag Cloud"
+
+#: .\templates\blog\tags\sidebar.html:107
+msgid "Welcome to star or fork the source code of this site"
+msgstr "Welcome to star or fork the source code of this site"
+
+#: .\templates\blog\tags\sidebar.html:118
+msgid "Function"
+msgstr "Function"
+
+#: .\templates\blog\tags\sidebar.html:120
+msgid "management site"
+msgstr "management site"
+
+#: .\templates\blog\tags\sidebar.html:122
+msgid "logout"
+msgstr "logout"
+
+#: .\templates\blog\tags\sidebar.html:129
+msgid "Track record"
+msgstr "Track record"
+
+#: .\templates\blog\tags\sidebar.html:135
+msgid "Click me to return to the top"
+msgstr "Click me to return to the top"
+
+#: .\templates\oauth\oauth_applications.html:5
+#| msgid "login"
+msgid "quick login"
+msgstr "quick login"
+
+#: .\templates\share_layout\nav.html:26
+msgid "Article archive"
+msgstr "Article archive"
diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po
new file mode 100644
index 000000000..200b7e6c0
--- /dev/null
+++ b/locale/zh_Hans/LC_MESSAGES/django.po
@@ -0,0 +1,667 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-09-13 16:02+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: .\accounts\admin.py:12
+msgid "password"
+msgstr "密码"
+
+#: .\accounts\admin.py:13
+msgid "Enter password again"
+msgstr "再次输入密码"
+
+#: .\accounts\admin.py:24 .\accounts\forms.py:89
+msgid "passwords do not match"
+msgstr "密码不匹配"
+
+#: .\accounts\forms.py:36
+msgid "email already exists"
+msgstr "邮箱已存在"
+
+#: .\accounts\forms.py:46 .\accounts\forms.py:50
+msgid "New password"
+msgstr "新密码"
+
+#: .\accounts\forms.py:60
+msgid "Confirm password"
+msgstr "确认密码"
+
+#: .\accounts\forms.py:70 .\accounts\forms.py:116
+msgid "Email"
+msgstr "邮箱"
+
+#: .\accounts\forms.py:76 .\accounts\forms.py:80
+msgid "Code"
+msgstr "验证码"
+
+#: .\accounts\forms.py:100 .\accounts\tests.py:194
+msgid "email does not exist"
+msgstr "邮箱不存在"
+
+#: .\accounts\models.py:12 .\oauth\models.py:17
+msgid "nick name"
+msgstr "昵称"
+
+#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266
+#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23
+#: .\oauth\models.py:53
+msgid "creation time"
+msgstr "创建时间"
+
+#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24
+#: .\oauth\models.py:54
+msgid "last modify time"
+msgstr "最后修改时间"
+
+#: .\accounts\models.py:15
+msgid "create source"
+msgstr "来源"
+
+#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81
+msgid "user"
+msgstr "用户"
+
+#: .\accounts\tests.py:216 .\accounts\utils.py:39
+msgid "Verification code error"
+msgstr "验证码错误"
+
+#: .\accounts\utils.py:13
+msgid "Verify Email"
+msgstr "验证邮箱"
+
+#: .\accounts\utils.py:21
+#, python-format
+msgid ""
+"You are resetting the password, the verification code is:%(code)s, valid "
+"within 5 minutes, please keep it properly"
+msgstr "您正在重置密码,验证码为:%(code)s,5分钟内有效 请妥善保管."
+
+#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17
+#: .\oauth\models.py:12
+msgid "author"
+msgstr "作者"
+
+#: .\blog\admin.py:53
+msgid "Publish selected articles"
+msgstr "发布选中的文章"
+
+#: .\blog\admin.py:54
+msgid "Draft selected articles"
+msgstr "选中文章设为草稿"
+
+#: .\blog\admin.py:55
+msgid "Close article comments"
+msgstr "关闭文章评论"
+
+#: .\blog\admin.py:56
+msgid "Open article comments"
+msgstr "打开文章评论"
+
+#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183
+#: .\templates\blog\tags\sidebar.html:40
+msgid "category"
+msgstr "分类目录"
+
+#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8
+msgid "index"
+msgstr "首页"
+
+#: .\blog\models.py:21
+msgid "list"
+msgstr "列表"
+
+#: .\blog\models.py:22
+msgid "post"
+msgstr "文章"
+
+#: .\blog\models.py:23
+msgid "all"
+msgstr "所有"
+
+#: .\blog\models.py:24
+msgid "slide"
+msgstr "侧边栏"
+
+#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285
+msgid "modify time"
+msgstr "修改时间"
+
+#: .\blog\models.py:63
+msgid "Draft"
+msgstr "草稿"
+
+#: .\blog\models.py:64
+msgid "Published"
+msgstr "发布"
+
+#: .\blog\models.py:67
+msgid "Open"
+msgstr "打开"
+
+#: .\blog\models.py:68
+msgid "Close"
+msgstr "关闭"
+
+#: .\blog\models.py:71 .\comments\admin.py:47
+msgid "Article"
+msgstr "文章"
+
+#: .\blog\models.py:72
+msgid "Page"
+msgstr "页面"
+
+#: .\blog\models.py:74 .\blog\models.py:280
+msgid "title"
+msgstr "标题"
+
+#: .\blog\models.py:75
+msgid "body"
+msgstr "内容"
+
+#: .\blog\models.py:77
+msgid "publish time"
+msgstr "发布时间"
+
+#: .\blog\models.py:79
+msgid "status"
+msgstr "状态"
+
+#: .\blog\models.py:84
+msgid "comment status"
+msgstr "评论状态"
+
+#: .\blog\models.py:88 .\oauth\models.py:43
+msgid "type"
+msgstr "类型"
+
+#: .\blog\models.py:89
+msgid "views"
+msgstr "阅读量"
+
+#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282
+msgid "order"
+msgstr "排序"
+
+#: .\blog\models.py:98
+msgid "show toc"
+msgstr "显示目录"
+
+#: .\blog\models.py:105 .\blog\models.py:249
+msgid "tag"
+msgstr "标签"
+
+#: .\blog\models.py:115 .\comments\models.py:21
+msgid "article"
+msgstr "文章"
+
+#: .\blog\models.py:171
+msgid "category name"
+msgstr "分类名"
+
+#: .\blog\models.py:174
+msgid "parent category"
+msgstr "上级分类"
+
+#: .\blog\models.py:234
+msgid "tag name"
+msgstr "标签名"
+
+#: .\blog\models.py:256
+msgid "link name"
+msgstr "链接名"
+
+#: .\blog\models.py:257 .\blog\models.py:271
+msgid "link"
+msgstr "链接"
+
+#: .\blog\models.py:260
+msgid "is show"
+msgstr "是否显示"
+
+#: .\blog\models.py:262
+msgid "show type"
+msgstr "显示类型"
+
+#: .\blog\models.py:281
+msgid "content"
+msgstr "内容"
+
+#: .\blog\models.py:283 .\oauth\models.py:52
+msgid "is enable"
+msgstr "是否启用"
+
+#: .\blog\models.py:289
+msgid "sidebar"
+msgstr "侧边栏"
+
+#: .\blog\models.py:299
+msgid "site name"
+msgstr "站点名称"
+
+#: .\blog\models.py:305
+msgid "site description"
+msgstr "站点描述"
+
+#: .\blog\models.py:311
+msgid "site seo description"
+msgstr "站点SEO描述"
+
+#: .\blog\models.py:313
+msgid "site keywords"
+msgstr "关键字"
+
+#: .\blog\models.py:318
+msgid "article sub length"
+msgstr "文章摘要长度"
+
+#: .\blog\models.py:319
+msgid "sidebar article count"
+msgstr "侧边栏文章数目"
+
+#: .\blog\models.py:320
+msgid "sidebar comment count"
+msgstr "侧边栏评论数目"
+
+#: .\blog\models.py:321
+msgid "article comment count"
+msgstr "文章页面默认显示评论数目"
+
+#: .\blog\models.py:322
+msgid "show adsense"
+msgstr "是否显示广告"
+
+#: .\blog\models.py:324
+msgid "adsense code"
+msgstr "广告内容"
+
+#: .\blog\models.py:325
+msgid "open site comment"
+msgstr "公共头部"
+
+#: .\blog\models.py:352
+msgid "Website configuration"
+msgstr "网站配置"
+
+#: .\blog\models.py:360
+msgid "There can only be one configuration"
+msgstr "只能有一个配置"
+
+#: .\blog\views.py:348
+msgid ""
+"Sorry, the page you requested is not found, please click the home page to "
+"see other?"
+msgstr "抱歉,你所访问的页面找不到,请点击首页看看别的?"
+
+#: .\blog\views.py:356
+msgid "Sorry, the server is busy, please click the home page to see other?"
+msgstr "抱歉,服务出错了,请点击首页看看别的?"
+
+#: .\blog\views.py:369
+msgid "Sorry, you do not have permission to access this page?"
+msgstr "抱歉,你没用权限访问此页面。"
+
+#: .\comments\admin.py:15
+msgid "Disable comments"
+msgstr "禁用评论"
+
+#: .\comments\admin.py:16
+msgid "Enable comments"
+msgstr "启用评论"
+
+#: .\comments\admin.py:46
+msgid "User"
+msgstr "用户"
+
+#: .\comments\models.py:25
+msgid "parent comment"
+msgstr "上级评论"
+
+#: .\comments\models.py:29
+msgid "enable"
+msgstr "启用"
+
+#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30
+msgid "comment"
+msgstr "评论"
+
+#: .\comments\utils.py:13
+msgid "Thanks for your comment"
+msgstr "感谢你的评论"
+
+#: .\comments\utils.py:15
+#, python-format
+msgid ""
+"Thank you very much for your comments on this site
\n"
+" You can visit %(article_title)s\n"
+" to review your comments,\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s"
+msgstr ""
+"非常感谢您对此网站的评论
\n"
+" 您可以访问%(article_title)s\n"
+"查看您的评论,\n"
+"再次感谢您!\n"
+"
\n"
+" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n"
+"%(article_url)s"
+
+#: .\comments\utils.py:26
+#, python-format
+msgid ""
+"Your comment on "
+"%(article_title)s
has \n"
+" received a reply.
%(comment_body)s\n"
+"
\n"
+" go check it out!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s\n"
+" "
+msgstr ""
+"您对 %(article_title)s
"
+"的评论有\n"
+" 收到回复。
%(comment_body)s\n"
+"
\n"
+"快去看看吧!\n"
+"
\n"
+" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n"
+" %(article_url)s\n"
+" "
+
+#: .\djangoblog\logentryadmin.py:63
+msgid "object"
+msgstr "对象"
+
+#: .\djangoblog\settings.py:140
+msgid "English"
+msgstr "英文"
+
+#: .\djangoblog\settings.py:141
+msgid "Simplified Chinese"
+msgstr "简体中文"
+
+#: .\djangoblog\settings.py:142
+msgid "Traditional Chinese"
+msgstr "繁体中文"
+
+#: .\oauth\models.py:30
+msgid "oauth user"
+msgstr "第三方用户"
+
+#: .\oauth\models.py:37
+msgid "weibo"
+msgstr "微博"
+
+#: .\oauth\models.py:38
+msgid "google"
+msgstr "谷歌"
+
+#: .\oauth\models.py:48
+msgid "callback url"
+msgstr "回调地址"
+
+#: .\oauth\models.py:59
+msgid "already exists"
+msgstr "已经存在"
+
+#: .\oauth\views.py:154
+#, python-format
+msgid ""
+"\n"
+" Congratulations, you have successfully bound your email address. You "
+"can use\n"
+" %(oauthuser_type)s to directly log in to this website without a "
+"password.
\n"
+" You are welcome to continue to follow this site, the address is\n"
+" %(site)s\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link to your "
+"browser.\n"
+" %(site)s\n"
+" "
+msgstr ""
+"\n"
+" 恭喜你已经绑定成功 你可以使用\n"
+" %(oauthuser_type)s 来免密登录本站
\n"
+" 欢迎继续关注本站, 地址是\n"
+" %(site)s\n"
+" 再次感谢你\n"
+"
\n"
+" 如果上面链接无法打开,请复制此链接到你的浏览器 \n"
+" %(site)s\n"
+" "
+
+#: .\oauth\views.py:165
+msgid "Congratulations on your successful binding!"
+msgstr "恭喜你绑定成功"
+
+#: .\oauth\views.py:217
+#, python-format
+msgid ""
+"\n"
+" Please click the link below to bind your email
\n"
+"\n"
+" %(url)s\n"
+"\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link "
+"to your browser.\n"
+"
\n"
+" %(url)s\n"
+" "
+msgstr ""
+"\n"
+" 请点击下面的链接绑定您的邮箱
\n"
+"\n"
+" %(url)s\n"
+"\n"
+"再次感谢您!\n"
+"
\n"
+"如果上面的链接打不开,请复制此链接到您的浏览器。\n"
+"%(url)s\n"
+" "
+
+#: .\oauth\views.py:228 .\oauth\views.py:240
+msgid "Bind your email"
+msgstr "绑定邮箱"
+
+#: .\oauth\views.py:242
+msgid ""
+"Congratulations, the binding is just one step away. Please log in to your "
+"email to check the email to complete the binding. Thank you."
+msgstr "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。"
+
+#: .\oauth\views.py:245
+msgid "Binding successful"
+msgstr "绑定成功"
+
+#: .\oauth\views.py:247
+#, python-format
+msgid ""
+"Congratulations, you have successfully bound your email address. You can use "
+"%(oauthuser_type)s to directly log in to this website without a password. "
+"You are welcome to continue to follow this site."
+msgstr ""
+"恭喜您绑定成功,您以后可以使用%(oauthuser_type)s来直接免密码登录本站啦,感谢"
+"您对本站对关注。"
+
+#: .\templates\account\forget_password.html:7
+msgid "forget the password"
+msgstr "忘记密码"
+
+#: .\templates\account\forget_password.html:18
+msgid "get verification code"
+msgstr "获取验证码"
+
+#: .\templates\account\forget_password.html:19
+msgid "submit"
+msgstr "提交"
+
+#: .\templates\account\login.html:36
+msgid "Create Account"
+msgstr "创建账号"
+
+#: .\templates\account\login.html:42
+#| msgid "forget the password"
+msgid "Forget Password"
+msgstr "忘记密码"
+
+#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126
+msgid "login"
+msgstr "登录"
+
+#: .\templates\account\result.html:22
+msgid "back to the homepage"
+msgstr "返回首页吧"
+
+#: .\templates\blog\article_archives.html:7
+#: .\templates\blog\article_archives.html:24
+msgid "article archive"
+msgstr "文章归档"
+
+#: .\templates\blog\article_archives.html:32
+msgid "year"
+msgstr "年"
+
+#: .\templates\blog\article_archives.html:36
+msgid "month"
+msgstr "月"
+
+#: .\templates\blog\tags\article_info.html:12
+msgid "pin to top"
+msgstr "置顶"
+
+#: .\templates\blog\tags\article_info.html:28
+msgid "comments"
+msgstr "评论"
+
+#: .\templates\blog\tags\article_info.html:58
+msgid "toc"
+msgstr "目录"
+
+#: .\templates\blog\tags\article_meta_info.html:6
+msgid "posted in"
+msgstr "发布于"
+
+#: .\templates\blog\tags\article_meta_info.html:14
+msgid "and tagged"
+msgstr "并标记为"
+
+#: .\templates\blog\tags\article_meta_info.html:25
+msgid "by "
+msgstr "由"
+
+#: .\templates\blog\tags\article_meta_info.html:29
+#, python-format
+msgid ""
+"\n"
+" title=\"View all articles published by "
+"%(article.author.username)s\"\n"
+" "
+msgstr ""
+"\n"
+" title=\"查看所有由 %(article.author.username)s\"发布的文章\n"
+" "
+
+#: .\templates\blog\tags\article_meta_info.html:44
+msgid "on"
+msgstr "在"
+
+#: .\templates\blog\tags\article_meta_info.html:54
+msgid "edit"
+msgstr "编辑"
+
+#: .\templates\blog\tags\article_pagination.html:4
+msgid "article navigation"
+msgstr "文章导航"
+
+#: .\templates\blog\tags\article_pagination.html:9
+msgid "earlier articles"
+msgstr "早期文章"
+
+#: .\templates\blog\tags\article_pagination.html:12
+msgid "newer articles"
+msgstr "较新文章"
+
+#: .\templates\blog\tags\article_tag_list.html:5
+msgid "tags"
+msgstr "标签"
+
+#: .\templates\blog\tags\sidebar.html:7
+msgid "search"
+msgstr "搜索"
+
+#: .\templates\blog\tags\sidebar.html:50
+msgid "recent comments"
+msgstr "近期评论"
+
+#: .\templates\blog\tags\sidebar.html:57
+msgid "published on"
+msgstr "发表于"
+
+#: .\templates\blog\tags\sidebar.html:65
+msgid "recent articles"
+msgstr "近期文章"
+
+#: .\templates\blog\tags\sidebar.html:77
+msgid "bookmark"
+msgstr "书签"
+
+#: .\templates\blog\tags\sidebar.html:96
+msgid "Tag Cloud"
+msgstr "标签云"
+
+#: .\templates\blog\tags\sidebar.html:107
+msgid "Welcome to star or fork the source code of this site"
+msgstr "欢迎您STAR或者FORK本站源代码"
+
+#: .\templates\blog\tags\sidebar.html:118
+msgid "Function"
+msgstr "功能"
+
+#: .\templates\blog\tags\sidebar.html:120
+msgid "management site"
+msgstr "管理站点"
+
+#: .\templates\blog\tags\sidebar.html:122
+msgid "logout"
+msgstr "登出"
+
+#: .\templates\blog\tags\sidebar.html:129
+msgid "Track record"
+msgstr "运动轨迹记录"
+
+#: .\templates\blog\tags\sidebar.html:135
+msgid "Click me to return to the top"
+msgstr "点我返回顶部"
+
+#: .\templates\oauth\oauth_applications.html:5
+#| msgid "login"
+msgid "quick login"
+msgstr "快捷登录"
+
+#: .\templates\share_layout\nav.html:26
+msgid "Article archive"
+msgstr "文章归档"
diff --git a/locale/zh_Hant/LC_MESSAGES/django.po b/locale/zh_Hant/LC_MESSAGES/django.po
new file mode 100644
index 000000000..a2920ce51
--- /dev/null
+++ b/locale/zh_Hant/LC_MESSAGES/django.po
@@ -0,0 +1,668 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-09-13 16:02+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: .\accounts\admin.py:12
+msgid "password"
+msgstr "密碼"
+
+#: .\accounts\admin.py:13
+msgid "Enter password again"
+msgstr "再次輸入密碼"
+
+#: .\accounts\admin.py:24 .\accounts\forms.py:89
+msgid "passwords do not match"
+msgstr "密碼不匹配"
+
+#: .\accounts\forms.py:36
+msgid "email already exists"
+msgstr "郵箱已存在"
+
+#: .\accounts\forms.py:46 .\accounts\forms.py:50
+msgid "New password"
+msgstr "新密碼"
+
+#: .\accounts\forms.py:60
+msgid "Confirm password"
+msgstr "確認密碼"
+
+#: .\accounts\forms.py:70 .\accounts\forms.py:116
+msgid "Email"
+msgstr "郵箱"
+
+#: .\accounts\forms.py:76 .\accounts\forms.py:80
+msgid "Code"
+msgstr "驗證碼"
+
+#: .\accounts\forms.py:100 .\accounts\tests.py:194
+msgid "email does not exist"
+msgstr "郵箱不存在"
+
+#: .\accounts\models.py:12 .\oauth\models.py:17
+msgid "nick name"
+msgstr "昵稱"
+
+#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266
+#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23
+#: .\oauth\models.py:53
+msgid "creation time"
+msgstr "創建時間"
+
+#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24
+#: .\oauth\models.py:54
+msgid "last modify time"
+msgstr "最後修改時間"
+
+#: .\accounts\models.py:15
+msgid "create source"
+msgstr "來源"
+
+#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81
+msgid "user"
+msgstr "用戶"
+
+#: .\accounts\tests.py:216 .\accounts\utils.py:39
+msgid "Verification code error"
+msgstr "驗證碼錯誤"
+
+#: .\accounts\utils.py:13
+msgid "Verify Email"
+msgstr "驗證郵箱"
+
+#: .\accounts\utils.py:21
+#, python-format
+msgid ""
+"You are resetting the password, the verification code is:%(code)s, valid "
+"within 5 minutes, please keep it properly"
+msgstr "您正在重置密碼,驗證碼為:%(code)s,5分鐘內有效 請妥善保管."
+
+#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17
+#: .\oauth\models.py:12
+msgid "author"
+msgstr "作者"
+
+#: .\blog\admin.py:53
+msgid "Publish selected articles"
+msgstr "發布選中的文章"
+
+#: .\blog\admin.py:54
+msgid "Draft selected articles"
+msgstr "選中文章設為草稿"
+
+#: .\blog\admin.py:55
+msgid "Close article comments"
+msgstr "關閉文章評論"
+
+#: .\blog\admin.py:56
+msgid "Open article comments"
+msgstr "打開文章評論"
+
+#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183
+#: .\templates\blog\tags\sidebar.html:40
+msgid "category"
+msgstr "分類目錄"
+
+#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8
+msgid "index"
+msgstr "首頁"
+
+#: .\blog\models.py:21
+msgid "list"
+msgstr "列表"
+
+#: .\blog\models.py:22
+msgid "post"
+msgstr "文章"
+
+#: .\blog\models.py:23
+msgid "all"
+msgstr "所有"
+
+#: .\blog\models.py:24
+msgid "slide"
+msgstr "側邊欄"
+
+#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285
+msgid "modify time"
+msgstr "修改時間"
+
+#: .\blog\models.py:63
+msgid "Draft"
+msgstr "草稿"
+
+#: .\blog\models.py:64
+msgid "Published"
+msgstr "發布"
+
+#: .\blog\models.py:67
+msgid "Open"
+msgstr "打開"
+
+#: .\blog\models.py:68
+msgid "Close"
+msgstr "關閉"
+
+#: .\blog\models.py:71 .\comments\admin.py:47
+msgid "Article"
+msgstr "文章"
+
+#: .\blog\models.py:72
+msgid "Page"
+msgstr "頁面"
+
+#: .\blog\models.py:74 .\blog\models.py:280
+msgid "title"
+msgstr "標題"
+
+#: .\blog\models.py:75
+msgid "body"
+msgstr "內容"
+
+#: .\blog\models.py:77
+msgid "publish time"
+msgstr "發布時間"
+
+#: .\blog\models.py:79
+msgid "status"
+msgstr "狀態"
+
+#: .\blog\models.py:84
+msgid "comment status"
+msgstr "評論狀態"
+
+#: .\blog\models.py:88 .\oauth\models.py:43
+msgid "type"
+msgstr "類型"
+
+#: .\blog\models.py:89
+msgid "views"
+msgstr "閱讀量"
+
+#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282
+msgid "order"
+msgstr "排序"
+
+#: .\blog\models.py:98
+msgid "show toc"
+msgstr "顯示目錄"
+
+#: .\blog\models.py:105 .\blog\models.py:249
+msgid "tag"
+msgstr "標簽"
+
+#: .\blog\models.py:115 .\comments\models.py:21
+msgid "article"
+msgstr "文章"
+
+#: .\blog\models.py:171
+msgid "category name"
+msgstr "分類名"
+
+#: .\blog\models.py:174
+msgid "parent category"
+msgstr "上級分類"
+
+#: .\blog\models.py:234
+msgid "tag name"
+msgstr "標簽名"
+
+#: .\blog\models.py:256
+msgid "link name"
+msgstr "鏈接名"
+
+#: .\blog\models.py:257 .\blog\models.py:271
+msgid "link"
+msgstr "鏈接"
+
+#: .\blog\models.py:260
+msgid "is show"
+msgstr "是否顯示"
+
+#: .\blog\models.py:262
+msgid "show type"
+msgstr "顯示類型"
+
+#: .\blog\models.py:281
+msgid "content"
+msgstr "內容"
+
+#: .\blog\models.py:283 .\oauth\models.py:52
+msgid "is enable"
+msgstr "是否啟用"
+
+#: .\blog\models.py:289
+msgid "sidebar"
+msgstr "側邊欄"
+
+#: .\blog\models.py:299
+msgid "site name"
+msgstr "站點名稱"
+
+#: .\blog\models.py:305
+msgid "site description"
+msgstr "站點描述"
+
+#: .\blog\models.py:311
+msgid "site seo description"
+msgstr "站點SEO描述"
+
+#: .\blog\models.py:313
+msgid "site keywords"
+msgstr "關鍵字"
+
+#: .\blog\models.py:318
+msgid "article sub length"
+msgstr "文章摘要長度"
+
+#: .\blog\models.py:319
+msgid "sidebar article count"
+msgstr "側邊欄文章數目"
+
+#: .\blog\models.py:320
+msgid "sidebar comment count"
+msgstr "側邊欄評論數目"
+
+#: .\blog\models.py:321
+msgid "article comment count"
+msgstr "文章頁面默認顯示評論數目"
+
+#: .\blog\models.py:322
+msgid "show adsense"
+msgstr "是否顯示廣告"
+
+#: .\blog\models.py:324
+msgid "adsense code"
+msgstr "廣告內容"
+
+#: .\blog\models.py:325
+msgid "open site comment"
+msgstr "公共頭部"
+
+#: .\blog\models.py:352
+msgid "Website configuration"
+msgstr "網站配置"
+
+#: .\blog\models.py:360
+msgid "There can only be one configuration"
+msgstr "只能有一個配置"
+
+#: .\blog\views.py:348
+msgid ""
+"Sorry, the page you requested is not found, please click the home page to "
+"see other?"
+msgstr "抱歉,你所訪問的頁面找不到,請點擊首頁看看別的?"
+
+#: .\blog\views.py:356
+msgid "Sorry, the server is busy, please click the home page to see other?"
+msgstr "抱歉,服務出錯了,請點擊首頁看看別的?"
+
+#: .\blog\views.py:369
+msgid "Sorry, you do not have permission to access this page?"
+msgstr "抱歉,你沒用權限訪問此頁面。"
+
+#: .\comments\admin.py:15
+msgid "Disable comments"
+msgstr "禁用評論"
+
+#: .\comments\admin.py:16
+msgid "Enable comments"
+msgstr "啟用評論"
+
+#: .\comments\admin.py:46
+msgid "User"
+msgstr "用戶"
+
+#: .\comments\models.py:25
+msgid "parent comment"
+msgstr "上級評論"
+
+#: .\comments\models.py:29
+msgid "enable"
+msgstr "啟用"
+
+#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30
+msgid "comment"
+msgstr "評論"
+
+#: .\comments\utils.py:13
+msgid "Thanks for your comment"
+msgstr "感謝你的評論"
+
+#: .\comments\utils.py:15
+#, python-format
+msgid ""
+"Thank you very much for your comments on this site
\n"
+" You can visit %(article_title)s\n"
+" to review your comments,\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s"
+msgstr ""
+"非常感謝您對此網站的評論
\n"
+" 您可以訪問%(article_title)s\n"
+"查看您的評論,\n"
+"再次感謝您!\n"
+"
\n"
+" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n"
+"%(article_url)s"
+
+#: .\comments\utils.py:26
+#, python-format
+msgid ""
+"Your comment on "
+"%(article_title)s
has \n"
+" received a reply.
%(comment_body)s\n"
+"
\n"
+" go check it out!\n"
+"
\n"
+" If the link above cannot be opened, please copy this "
+"link to your browser.\n"
+" %(article_url)s\n"
+" "
+msgstr ""
+"您對 %(article_title)s
"
+"的評論有\n"
+" 收到回復。
%(comment_body)s\n"
+"
\n"
+"快去看看吧!\n"
+"
\n"
+" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n"
+" %(article_url)s\n"
+" "
+
+#: .\djangoblog\logentryadmin.py:63
+msgid "object"
+msgstr "對象"
+
+#: .\djangoblog\settings.py:140
+msgid "English"
+msgstr "英文"
+
+#: .\djangoblog\settings.py:141
+msgid "Simplified Chinese"
+msgstr "簡體中文"
+
+#: .\djangoblog\settings.py:142
+msgid "Traditional Chinese"
+msgstr "繁體中文"
+
+#: .\oauth\models.py:30
+msgid "oauth user"
+msgstr "第三方用戶"
+
+#: .\oauth\models.py:37
+msgid "weibo"
+msgstr "微博"
+
+#: .\oauth\models.py:38
+msgid "google"
+msgstr "谷歌"
+
+#: .\oauth\models.py:48
+msgid "callback url"
+msgstr "回調地址"
+
+#: .\oauth\models.py:59
+msgid "already exists"
+msgstr "已經存在"
+
+#: .\oauth\views.py:154
+#, python-format
+msgid ""
+"\n"
+" Congratulations, you have successfully bound your email address. You "
+"can use\n"
+" %(oauthuser_type)s to directly log in to this website without a "
+"password.
\n"
+" You are welcome to continue to follow this site, the address is\n"
+" %(site)s\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link to your "
+"browser.\n"
+" %(site)s\n"
+" "
+msgstr ""
+"\n"
+" 恭喜你已經綁定成功 你可以使用\n"
+" %(oauthuser_type)s 來免密登錄本站
\n"
+" 歡迎繼續關註本站, 地址是\n"
+" %(site)s\n"
+" 再次感謝你\n"
+"
\n"
+" 如果上面鏈接無法打開,請復製此鏈接到你的瀏覽器 \n"
+" %(site)s\n"
+" "
+
+#: .\oauth\views.py:165
+msgid "Congratulations on your successful binding!"
+msgstr "恭喜你綁定成功"
+
+#: .\oauth\views.py:217
+#, python-format
+msgid ""
+"\n"
+" Please click the link below to bind your email
\n"
+"\n"
+" %(url)s\n"
+"\n"
+" Thank you again!\n"
+"
\n"
+" If the link above cannot be opened, please copy this link "
+"to your browser.\n"
+"
\n"
+" %(url)s\n"
+" "
+msgstr ""
+"\n"
+" 請點擊下面的鏈接綁定您的郵箱
\n"
+"\n"
+" %(url)s\n"
+"\n"
+"再次感謝您!\n"
+"
\n"
+"如果上面的鏈接打不開,請復製此鏈接到您的瀏覽器。\n"
+"%(url)s\n"
+" "
+
+#: .\oauth\views.py:228 .\oauth\views.py:240
+msgid "Bind your email"
+msgstr "綁定郵箱"
+
+#: .\oauth\views.py:242
+msgid ""
+"Congratulations, the binding is just one step away. Please log in to your "
+"email to check the email to complete the binding. Thank you."
+msgstr "恭喜您,還差一步就綁定成功了,請登錄您的郵箱查看郵件完成綁定,謝謝。"
+
+#: .\oauth\views.py:245
+msgid "Binding successful"
+msgstr "綁定成功"
+
+#: .\oauth\views.py:247
+#, python-format
+msgid ""
+"Congratulations, you have successfully bound your email address. You can use "
+"%(oauthuser_type)s to directly log in to this website without a password. "
+"You are welcome to continue to follow this site."
+msgstr ""
+"恭喜您綁定成功,您以後可以使用%(oauthuser_type)s來直接免密碼登錄本站啦,感謝"
+"您對本站對關註。"
+
+#: .\templates\account\forget_password.html:7
+msgid "forget the password"
+msgstr "忘記密碼"
+
+#: .\templates\account\forget_password.html:18
+msgid "get verification code"
+msgstr "獲取驗證碼"
+
+#: .\templates\account\forget_password.html:19
+msgid "submit"
+msgstr "提交"
+
+#: .\templates\account\login.html:36
+msgid "Create Account"
+msgstr "創建賬號"
+
+#: .\templates\account\login.html:42
+#, fuzzy
+#| msgid "forget the password"
+msgid "Forget Password"
+msgstr "忘記密碼"
+
+#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126
+msgid "login"
+msgstr "登錄"
+
+#: .\templates\account\result.html:22
+msgid "back to the homepage"
+msgstr "返回首頁吧"
+
+#: .\templates\blog\article_archives.html:7
+#: .\templates\blog\article_archives.html:24
+msgid "article archive"
+msgstr "文章歸檔"
+
+#: .\templates\blog\article_archives.html:32
+msgid "year"
+msgstr "年"
+
+#: .\templates\blog\article_archives.html:36
+msgid "month"
+msgstr "月"
+
+#: .\templates\blog\tags\article_info.html:12
+msgid "pin to top"
+msgstr "置頂"
+
+#: .\templates\blog\tags\article_info.html:28
+msgid "comments"
+msgstr "評論"
+
+#: .\templates\blog\tags\article_info.html:58
+msgid "toc"
+msgstr "目錄"
+
+#: .\templates\blog\tags\article_meta_info.html:6
+msgid "posted in"
+msgstr "發布於"
+
+#: .\templates\blog\tags\article_meta_info.html:14
+msgid "and tagged"
+msgstr "並標記為"
+
+#: .\templates\blog\tags\article_meta_info.html:25
+msgid "by "
+msgstr "由"
+
+#: .\templates\blog\tags\article_meta_info.html:29
+#, python-format
+msgid ""
+"\n"
+" title=\"View all articles published by "
+"%(article.author.username)s\"\n"
+" "
+msgstr ""
+"\n"
+" title=\"查看所有由 %(article.author.username)s\"發布的文章\n"
+" "
+
+#: .\templates\blog\tags\article_meta_info.html:44
+msgid "on"
+msgstr "在"
+
+#: .\templates\blog\tags\article_meta_info.html:54
+msgid "edit"
+msgstr "編輯"
+
+#: .\templates\blog\tags\article_pagination.html:4
+msgid "article navigation"
+msgstr "文章導航"
+
+#: .\templates\blog\tags\article_pagination.html:9
+msgid "earlier articles"
+msgstr "早期文章"
+
+#: .\templates\blog\tags\article_pagination.html:12
+msgid "newer articles"
+msgstr "較新文章"
+
+#: .\templates\blog\tags\article_tag_list.html:5
+msgid "tags"
+msgstr "標簽"
+
+#: .\templates\blog\tags\sidebar.html:7
+msgid "search"
+msgstr "搜索"
+
+#: .\templates\blog\tags\sidebar.html:50
+msgid "recent comments"
+msgstr "近期評論"
+
+#: .\templates\blog\tags\sidebar.html:57
+msgid "published on"
+msgstr "發表於"
+
+#: .\templates\blog\tags\sidebar.html:65
+msgid "recent articles"
+msgstr "近期文章"
+
+#: .\templates\blog\tags\sidebar.html:77
+msgid "bookmark"
+msgstr "書簽"
+
+#: .\templates\blog\tags\sidebar.html:96
+msgid "Tag Cloud"
+msgstr "標簽雲"
+
+#: .\templates\blog\tags\sidebar.html:107
+msgid "Welcome to star or fork the source code of this site"
+msgstr "歡迎您STAR或者FORK本站源代碼"
+
+#: .\templates\blog\tags\sidebar.html:118
+msgid "Function"
+msgstr "功能"
+
+#: .\templates\blog\tags\sidebar.html:120
+msgid "management site"
+msgstr "管理站點"
+
+#: .\templates\blog\tags\sidebar.html:122
+msgid "logout"
+msgstr "登出"
+
+#: .\templates\blog\tags\sidebar.html:129
+msgid "Track record"
+msgstr "運動軌跡記錄"
+
+#: .\templates\blog\tags\sidebar.html:135
+msgid "Click me to return to the top"
+msgstr "點我返回頂部"
+
+#: .\templates\oauth\oauth_applications.html:5
+#| msgid "login"
+msgid "quick login"
+msgstr "快捷登錄"
+
+#: .\templates\share_layout\nav.html:26
+msgid "Article archive"
+msgstr "文章歸檔"
diff --git a/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
new file mode 100644
index 000000000..d5cc70ef2
--- /dev/null
+++ b/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
@@ -0,0 +1,86 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:13
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('oauth', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='oauthconfig',
+ options={'ordering': ['-creation_time'], 'verbose_name': 'oauth配置', 'verbose_name_plural': 'oauth配置'},
+ ),
+ migrations.AlterModelOptions(
+ name='oauthuser',
+ options={'ordering': ['-creation_time'], 'verbose_name': 'oauth user', 'verbose_name_plural': 'oauth user'},
+ ),
+ migrations.RemoveField(
+ model_name='oauthconfig',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='oauthconfig',
+ name='last_mod_time',
+ ),
+ migrations.RemoveField(
+ model_name='oauthuser',
+ name='created_time',
+ ),
+ migrations.RemoveField(
+ model_name='oauthuser',
+ name='last_mod_time',
+ ),
+ migrations.AddField(
+ model_name='oauthconfig',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='oauthconfig',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
+ ),
+ migrations.AddField(
+ model_name='oauthuser',
+ name='creation_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='oauthuser',
+ name='last_modify_time',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
+ ),
+ migrations.AlterField(
+ model_name='oauthconfig',
+ name='callback_url',
+ field=models.CharField(default='', max_length=200, verbose_name='callback url'),
+ ),
+ migrations.AlterField(
+ model_name='oauthconfig',
+ name='is_enable',
+ field=models.BooleanField(default=True, verbose_name='is enable'),
+ ),
+ migrations.AlterField(
+ model_name='oauthconfig',
+ name='type',
+ field=models.CharField(choices=[('weibo', 'weibo'), ('google', 'google'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='type'),
+ ),
+ migrations.AlterField(
+ model_name='oauthuser',
+ name='author',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
+ ),
+ migrations.AlterField(
+ model_name='oauthuser',
+ name='nickname',
+ field=models.CharField(max_length=50, verbose_name='nickname'),
+ ),
+ ]
diff --git a/oauth/models.py b/oauth/models.py
index ff82607d0..be838edd5 100644
--- a/oauth/models.py
+++ b/oauth/models.py
@@ -9,54 +9,54 @@
class OAuthUser(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
- verbose_name='用户',
+ verbose_name=_('author'),
blank=True,
null=True,
on_delete=models.CASCADE)
openid = models.CharField(max_length=50)
- nickname = models.CharField(max_length=50, verbose_name='昵称')
+ nickname = models.CharField(max_length=50, verbose_name=_('nick name'))
token = models.CharField(max_length=150, null=True, blank=True)
picture = models.CharField(max_length=350, blank=True, null=True)
type = models.CharField(blank=False, null=False, max_length=50)
email = models.CharField(max_length=50, null=True, blank=True)
metadata = models.TextField(null=True, blank=True)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_modify_time = models.DateTimeField(_('last modify time'), default=now)
def __str__(self):
return self.nickname
class Meta:
- verbose_name = 'oauth用户'
+ verbose_name = _('oauth user')
verbose_name_plural = verbose_name
- ordering = ['-created_time']
+ ordering = ['-creation_time']
class OAuthConfig(models.Model):
TYPE = (
- ('weibo', '微博'),
- ('google', '谷歌'),
+ ('weibo', _('weibo')),
+ ('google', _('google')),
('github', 'GitHub'),
('facebook', 'FaceBook'),
('qq', 'QQ'),
)
- type = models.CharField('类型', max_length=10, choices=TYPE, default='a')
+ type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a')
appkey = models.CharField(max_length=200, verbose_name='AppKey')
appsecret = models.CharField(max_length=200, verbose_name='AppSecret')
callback_url = models.CharField(
max_length=200,
- verbose_name='回调地址',
+ verbose_name=_('callback url'),
blank=False,
- default='http://www.baidu.com')
+ default='')
is_enable = models.BooleanField(
- '是否显示', default=True, blank=False, null=False)
- created_time = models.DateTimeField('创建时间', default=now)
- last_mod_time = models.DateTimeField('修改时间', default=now)
+ _('is enable'), default=True, blank=False, null=False)
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_modify_time = models.DateTimeField(_('last modify time'), default=now)
def clean(self):
if OAuthConfig.objects.filter(
type=self.type).exclude(id=self.id).count():
- raise ValidationError(_(self.type + '已经存在'))
+ raise ValidationError(_(self.type + _('already exists')))
def __str__(self):
return self.type
@@ -64,4 +64,4 @@ def __str__(self):
class Meta:
verbose_name = 'oauth配置'
verbose_name_plural = verbose_name
- ordering = ['-created_time']
+ ordering = ['-creation_time']
diff --git a/oauth/views.py b/oauth/views.py
index 00ec4c990..12e3a6ea1 100644
--- a/oauth/views.py
+++ b/oauth/views.py
@@ -13,6 +13,7 @@
from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
+from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from djangoblog.blog_signals import oauth_user_login_signal
@@ -149,19 +150,19 @@ def emailconfirm(request, id, sign):
id=oauthuser.id)
login(request, author)
- site = get_current_site().domain
- content = '''
- 恭喜您,您已经成功绑定您的邮箱,您可以使用{type}来直接免密码登录本网站.欢迎您继续关注本站,地址是
-
- {url}
-
- 再次感谢您!
-
- 如果上面链接无法打开,请将此链接复制至浏览器。
- {url}
- '''.format(type=oauthuser.type, url='http://' + site)
-
- send_email(emailto=[oauthuser.email, ], title='恭喜您绑定成功!', content=content)
+ site = 'http://' + get_current_site().domain
+ content = _('''
+ Congratulations, you have successfully bound your email address. You can use
+ %(oauthuser_type)s to directly log in to this website without a password.
+ You are welcome to continue to follow this site, the address is
+ %(site)s
+ Thank you again!
+
+ If the link above cannot be opened, please copy this link to your browser.
+ %(site)s
+ ''') % {'oauthuser_type': oauthuser.type, 'site': site}
+
+ send_email(emailto=[oauthuser.email, ], title=_('Congratulations on your successful binding!'), content=content)
url = reverse('oauth:bindsuccess', kwargs={
'oauthid': id
})
@@ -213,17 +214,18 @@ def form_valid(self, form):
})
url = "http://{site}{path}".format(site=site, path=path)
- content = """
- 请点击下面链接绑定您的邮箱
+ content = _("""
+ Please click the link below to bind your email
- {url}
+ %(url)s
- 再次感谢您!
-
- 如果上面链接无法打开,请将此链接复制至浏览器。
- {url}
- """.format(url=url)
- send_email(emailto=[email, ], title='绑定您的电子邮箱', content=content)
+ Thank you again!
+
+ If the link above cannot be opened, please copy this link to your browser.
+
+ %(url)s
+ """) % {'url': url}
+ send_email(emailto=[email, ], title=_('Bind your email'), content=content)
url = reverse('oauth:bindsuccess', kwargs={
'oauthid': oauthid
})
@@ -235,12 +237,16 @@ def bindsuccess(request, oauthid):
type = request.GET.get('type', None)
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
if type == 'email':
- title = '绑定成功'
- content = "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。"
+ title = _('Bind your email')
+ content = _(
+ 'Congratulations, the binding is just one step away. '
+ 'Please log in to your email to check the email to complete the binding. Thank you.')
else:
- title = '绑定成功'
- content = "恭喜您绑定成功,您以后可以使用{type}来直接免密码登录本站啦,感谢您对本站对关注。".format(
- type=oauthuser.type)
+ title = _('Binding successful')
+ content = _(
+ "Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s"
+ " to directly log in to this website without a password. You are welcome to continue to follow this site." % {
+ 'oauthuser_type': oauthuser.type})
return render(request, 'oauth/bindsuccess.html', {
'title': title,
'content': content
diff --git a/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
new file mode 100644
index 000000000..b4f8decc5
--- /dev/null
+++ b/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:19
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('owntracks', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='owntracklog',
+ options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'},
+ ),
+ migrations.RenameField(
+ model_name='owntracklog',
+ old_name='created_time',
+ new_name='creation_time',
+ ),
+ ]
diff --git a/owntracks/models.py b/owntracks/models.py
index 30f6ec17e..760942c66 100644
--- a/owntracks/models.py
+++ b/owntracks/models.py
@@ -8,13 +8,13 @@ class OwnTrackLog(models.Model):
tid = models.CharField(max_length=100, null=False, verbose_name='用户')
lat = models.FloatField(verbose_name='纬度')
lon = models.FloatField(verbose_name='经度')
- created_time = models.DateTimeField('创建时间', default=now)
+ creation_time = models.DateTimeField('创建时间', default=now)
def __str__(self):
return self.tid
class Meta:
- ordering = ['created_time']
+ ordering = ['creation_time']
verbose_name = "OwnTrackLogs"
verbose_name_plural = verbose_name
- get_latest_by = 'created_time'
+ get_latest_by = 'creation_time'
diff --git a/owntracks/views.py b/owntracks/views.py
index 27a89c8cd..2bb6e8a5a 100644
--- a/owntracks/views.py
+++ b/owntracks/views.py
@@ -5,12 +5,14 @@
import logging
from itertools import groupby
+import django.utils.timezone
import requests
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.http import JsonResponse
from django.shortcuts import render
from django.utils import timezone
+from django.utils.timezone import utc
from django.views.decorators.csrf import csrf_exempt
from .models import OwnTrackLog
@@ -59,7 +61,7 @@ def show_maps(request):
@login_required
def show_log_dates(request):
- dates = OwnTrackLog.objects.values_list('created_time', flat=True)
+ dates = OwnTrackLog.objects.values_list('creation_time', flat=True)
results = list(sorted(set(map(lambda x: x.strftime('%Y-%m-%d'), dates))))
context = {
@@ -95,9 +97,6 @@ def convert_to_amap(locations):
@login_required
def get_datas(request):
- import django.utils.timezone
- from django.utils.timezone import utc
-
now = django.utils.timezone.now().replace(tzinfo=utc)
querydate = django.utils.timezone.datetime(
now.year, now.month, now.day, 0, 0, 0)
@@ -108,7 +107,7 @@ def get_datas(request):
querydate = django.utils.timezone.make_aware(querydate)
nextdate = querydate + datetime.timedelta(days=1)
models = OwnTrackLog.objects.filter(
- created_time__range=(querydate, nextdate))
+ creation_time__range=(querydate, nextdate))
result = list()
if models and len(models):
for tid, item in groupby(
@@ -118,7 +117,7 @@ def get_datas(request):
d["name"] = tid
paths = list()
locations = convert_to_amap(
- sorted(item, key=lambda x: x.created_time))
+ sorted(item, key=lambda x: x.creation_time))
for i in locations.split(';'):
paths.append(i.split(','))
d["path"] = paths
diff --git a/requirements.txt b/requirements.txt
index 5a2230f05..1a86654a2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-coverage==7.3.0
+coverage==7.3.1
bleach==6.0.0
Django==4.2.5
django-compressor==4.4
@@ -8,7 +8,7 @@ django-mdeditor==0.1.20
django-uuslug==2.0.0
elasticsearch==7.16.1
elasticsearch-dsl==7.4.0
-gevent==23.9.0.post1
+gevent==23.9.1
jieba==0.42.1
jsonpickle==3.0.2
Markdown==3.4.4
@@ -17,7 +17,7 @@ Pillow==10.0.0
Pygments==2.16.1
python-logstash==0.4.8
python-slugify==8.0.1
-pytz==2023.3
+pytz==2023.3.post1
requests==2.31.0
WeRoBot==1.13.1
Whoosh==2.7.4
diff --git a/servermanager/admin.py b/servermanager/admin.py
index 361b92351..f26f4f6be 100644
--- a/servermanager/admin.py
+++ b/servermanager/admin.py
@@ -7,12 +7,12 @@ class CommandsAdmin(admin.ModelAdmin):
class EmailSendLogAdmin(admin.ModelAdmin):
- list_display = ('title', 'emailto', 'send_result', 'created_time')
+ list_display = ('title', 'emailto', 'send_result', 'creation_time')
readonly_fields = (
'title',
'emailto',
'send_result',
- 'created_time',
+ 'creation_time',
'content')
def has_add_permission(self, request):
diff --git a/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
new file mode 100644
index 000000000..48588574b
--- /dev/null
+++ b/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
@@ -0,0 +1,32 @@
+# Generated by Django 4.2.5 on 2023-09-06 13:19
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('servermanager', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='emailsendlog',
+ options={'ordering': ['-creation_time'], 'verbose_name': '邮件发送log', 'verbose_name_plural': '邮件发送log'},
+ ),
+ migrations.RenameField(
+ model_name='commands',
+ old_name='created_time',
+ new_name='creation_time',
+ ),
+ migrations.RenameField(
+ model_name='commands',
+ old_name='last_mod_time',
+ new_name='last_modify_time',
+ ),
+ migrations.RenameField(
+ model_name='emailsendlog',
+ old_name='created_time',
+ new_name='creation_time',
+ ),
+ ]
diff --git a/servermanager/models.py b/servermanager/models.py
index f75c93069..4326c6582 100644
--- a/servermanager/models.py
+++ b/servermanager/models.py
@@ -6,8 +6,8 @@ class commands(models.Model):
title = models.CharField('命令标题', max_length=300)
command = models.CharField('命令', max_length=2000)
describe = models.CharField('命令描述', max_length=300)
- created_time = models.DateTimeField('创建时间', auto_now_add=True)
- last_mod_time = models.DateTimeField('修改时间', auto_now=True)
+ creation_time = models.DateTimeField('创建时间', auto_now_add=True)
+ last_modify_time = models.DateTimeField('修改时间', auto_now=True)
def __str__(self):
return self.title
@@ -22,7 +22,7 @@ class EmailSendLog(models.Model):
title = models.CharField('邮件标题', max_length=2000)
content = models.TextField('邮件内容')
send_result = models.BooleanField('结果', default=False)
- created_time = models.DateTimeField('创建时间', auto_now_add=True)
+ creation_time = models.DateTimeField('创建时间', auto_now_add=True)
def __str__(self):
return self.title
@@ -30,4 +30,4 @@ def __str__(self):
class Meta:
verbose_name = '邮件发送log'
verbose_name_plural = verbose_name
- ordering = ['-created_time']
+ ordering = ['-creation_time']
diff --git a/servermanager/tests.py b/servermanager/tests.py
index 40fa9ec99..22a66892e 100644
--- a/servermanager/tests.py
+++ b/servermanager/tests.py
@@ -30,8 +30,6 @@ def test_validate_comment(self):
c = Category()
c.name = "categoryccc"
- c.created_time = timezone.now()
- c.last_mod_time = timezone.now()
c.save()
article = Article()
diff --git a/templates/account/forget_password.html b/templates/account/forget_password.html
index 1016c14a0..338453155 100644
--- a/templates/account/forget_password.html
+++ b/templates/account/forget_password.html
@@ -1,9 +1,10 @@
{% extends 'share_layout/base_account.html' %}
+{% load i18n %}
{% load static %}
{% block content %}
-
忘记密码
+
{% trans 'forget the password' %}
diff --git a/templates/account/login.html b/templates/account/login.html
index 1773896a9..cff8d3342 100644
--- a/templates/account/login.html
+++ b/templates/account/login.html
@@ -1,5 +1,6 @@
{% extends 'share_layout/base_account.html' %}
{% load static %}
+{% load i18n %}
{% block content %}
@@ -9,10 +10,6 @@
Sign in with your Account
- Create Account
+
+ {% trans 'Create Account' %}
+
|
Home Page
|
- 忘记密码
+
+ {% trans 'Forget Password' %}
+
diff --git a/templates/account/result.html b/templates/account/result.html
index 4bee77c63..23c909431 100644
--- a/templates/account/result.html
+++ b/templates/account/result.html
@@ -1,4 +1,5 @@
{% extends 'share_layout/base.html' %}
+{% load i18n %}
{% block header %}
{{ title }}
{% endblock %}
@@ -13,9 +14,13 @@ {{ content }}
diff --git a/templates/blog/article_archives.html b/templates/blog/article_archives.html
index 971e2c51d..959319eee 100644
--- a/templates/blog/article_archives.html
+++ b/templates/blog/article_archives.html
@@ -1,9 +1,10 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
+{% load i18n %}
{% block header %}
- 文章归档 | {{ SITE_DESCRIPTION }}
+ {% trans 'article archive' %} | {{ SITE_DESCRIPTION }}
@@ -20,7 +21,7 @@
@@ -28,11 +29,11 @@
{% regroup article_list by pub_time.year as year_post_group %}
{% for year in year_post_group %}
- - {{ year.grouper }} 年
+
- {{ year.grouper }} {% trans 'year' %}
{% regroup year.list by pub_time.month as month_post_group %}
{% for month in month_post_group %}
- - {{ month.grouper }} 月
+
- {{ month.grouper }} {% trans 'month' %}
{% for article in month.list %}
- {{ article.title }}
diff --git a/templates/blog/tags/article_info.html b/templates/blog/tags/article_info.html
index 993c978c2..3deec44fd 100644
--- a/templates/blog/tags/article_info.html
+++ b/templates/blog/tags/article_info.html
@@ -1,5 +1,6 @@
{% load blog_tags %}
{% load cache %}
+{% load i18n %}
{% endif %}
{% if page_obj.has_previous and previous_url %}
-