实操多语言博客
一、今日学习内容概述
二、模型设计
# models.py
from django.db import models
from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
class Category(models.Model):
"""文章分类"""
name = models.CharField(_('名称'), max_length=100)
slug = models.SlugField(_('URL标识'), unique=True)
class Meta:
verbose_name = _('分类')
verbose_name_plural = _('分类')
def __str__(self):
return self.name
class Post(models.Model):
"""博客文章"""
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
verbose_name=_('分类')
)
created_at = models.DateTimeField(_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(_('更新时间'), auto_now=True)
is_active = models.BooleanField(_('是否激活'), default=True)
class Meta:
verbose_name = _('文章')
verbose_name_plural = _('文章')
def get_absolute_url(self):
return reverse('post_detail', args=[str(self.id)])
class PostTranslation(models.Model):
"""文章翻译"""
post = models.ForeignKey(
Post,
on_delete=models.CASCADE,
related_name='translations'
)
language = models.CharField(
_('语言'),
max_length=10,
choices=settings.LANGUAGES
)
title = models.CharField(_('标题'), max_length=200)
content = models.TextField(_('内容'))
slug = models.SlugField(_('URL标识'), max_length=200)
class Meta:
unique_together = ('post', 'language')
verbose_name = _('文章翻译')
verbose_name_plural = _('文章翻译')
三、视图实现
# views.py
from django.shortcuts import render, get_object_or_404
from django.utils.translation import get_language
from django.views.generic import ListView, DetailView
from .models import Post, PostTranslation
class PostListView(ListView):
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
language = get_language()
return Post.objects.filter(
translations__language=language,
is_active=True
).select_related(
'category'
).prefetch_related(
'translations'
)
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
def get_object(self, queryset=None):
post = super().get_object(queryset)
language = get_language()
translation = get_object_or_404(
PostTranslation,
post=post,
language=language
)
post.translation = translation
return post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['available_languages'] = PostTranslation.objects.filter(
post=self.object
).values_list('language', flat=True)
return context
四、URL配置
# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include
from . import views
urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
]
urlpatterns += i18n_patterns(
path('', views.PostListView.as_view(), name='post_list'),
path('<int:pk>/<slug:slug>/',
views.PostDetailView.as_view(),
name='post_detail'),
prefix_default_language=False
)
五、模板实现
<!-- templates/blog/base.html -->
{% load i18n %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% trans "多语言博客" %}{% endblock %}</title>
</head>
<body>
<header>
<nav>
<form action="{% url 'set_language' %}" method="post" class="language-form">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.path }}">
<select name="language" onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% for lang_code, lang_name in LANGUAGES %}
<option value="{{ lang_code }}"
{% if lang_code == LANGUAGE_CODE %}selected{% endif %}>
{{ lang_name }}
</option>
{% endfor %}
</select>
</form>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
<!-- templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% load i18n %}
{% block content %}
<div class="container">
<h1>{% trans "文章列表" %}</h1>
{% for post in posts %}
<article class="post-preview">
<h2>{{ post.translations.title }}</h2>
<p class="meta">
{% blocktrans with category=post.category.name date=post.created_at %}
分类:{{ category }} | 发布于:{{ date }}
{% endblocktrans %}
</p>
<div class="excerpt">
{{ post.translations.content|truncatewords:50 }}
</div>
<a href="{{ post.get_absolute_url }}" class="read-more">
{% trans "阅读更多" %}
</a>
</article>
{% endfor %}
</div>
{% endblock %}
六、流程图
七、管理接口
# admin.py
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Category, Post, PostTranslation
class PostTranslationInline(admin.StackedInline):
model = PostTranslation
extra = 1
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('get_title', 'category', 'created_at', 'is_active')
list_filter = ('is_active', 'category', 'created_at')
search_fields = ('translations__title', 'translations__content')
inlines = [PostTranslationInline]
def get_title(self, obj):
default_trans = obj.translations.filter(
language=settings.LANGUAGE_CODE
).first()
return default_trans.title if default_trans else _('无标题')
get_title.short_description = _('标题')
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
prepopulated_fields = {'slug': ('name',)}
八、表单处理
# forms.py
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import Post, PostTranslation
class PostTranslationForm(forms.ModelForm):
class Meta:
model = PostTranslation
fields = ['title', 'content', 'slug']
def clean_slug(self):
slug = self.cleaned_data['slug']
language = self.cleaned_data.get('language')
if PostTranslation.objects.filter(
slug=slug,
language=language
).exists():
raise forms.ValidationError(_('此URL标识已被使用'))
return slug
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['category', 'is_active']
九、中间件
# middleware.py
from django.utils import translation
from django.conf import settings
from django.urls import resolve
class LanguageMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 检查URL中的语言代码
resolved = resolve(request.path_info)
language = resolved.kwargs.get('language')
if language:
# 如果URL中包含语言代码,则设置语言
translation.activate(language)
request.LANGUAGE_CODE = language
else:
# 否则使用默认语言或用户偏好
language = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
if language:
translation.activate(language)
request.LANGUAGE_CODE = language
response = self.get_response(request)
return response
十、工具函数
# utils.py
from django.utils import translation
from django.conf import settings
def get_translated_field(obj, field_name, language=None):
"""获取翻译字段的值"""
if language is None:
language = translation.get_language()
try:
trans = obj.translations.get(language=language)
return getattr(trans, field_name)
except obj.translations.model.DoesNotExist:
# 如果找不到翻译,返回默认语言的值
try:
trans = obj.translations.get(
language=settings.LANGUAGE_CODE
)
return getattr(trans, field_name)
except obj.translations.model.DoesNotExist:
return None
def copy_translation(obj, from_lang, to_lang):
"""复制翻译"""
try:
source = obj.translations.get(language=from_lang)
target, created = obj.translations.get_or_create(
language=to_lang
)
if not created:
return False
for field in ['title', 'content', 'slug']:
setattr(target, field, getattr(source, field))
target.save()
return True
except obj.translations.model.DoesNotExist:
return False
通过本章学习,你应该能够:
- 实现多语言博客系统
- 管理翻译内容
- 处理URL国际化
- 优化多语言用户体验
怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!