やりたいこと
ユーザーに言語やタイムゾーンを設定させたい
Djangoファンの皆さん、ドジャンゴー(挨拶)。
私達はDjangoがI18NやL10Nに対応していることをよく知っています。そして、いともたやすくそれらの力を利用できることを知っているはずです。
多言語翻訳が必要なら USE_I18N = True として LANGUAGE_CODEを指定、フォーマットローカライゼーションが必要なら USE_L10N = True、タイムゾーンをあわせたいなら USE_TZ = True として TIMEZONE を指定するだけなんですから。
そして、Google翻訳やDeepL翻訳にお願いして翻訳ファイルを作っていくだけでグローバルなアプリケーションを作り上げることができます。もしあなたがDjangoを使ってアプリケーションを作っているのなら、すぐにでも国際展開を念頭においたアプリケーションを作ることができます。
しかし、問題があります。
もし、国際展開を念頭においたアプリをあなたが作ったとして、日本人、アメリカ人、フランス人、アジア諸国の人が利用していたとして、その言語設定は一体どこで切り替えたら良いのでしょうか。
そう、settings.py です。しかしLANGUAGE_CODEの設定をいじってしまったら最後、指定した言語でDjangoは出力を行います。もしLANGUAGE_CODEをjaとした場合は、各国それぞれの人にDjangoは日本語で表示を行います。そうなると、一体なんのために国際化したのかよくわからなくなってしまいます。
個人的にもこれは不思議でなりませんが、Django標準ではLanguageはおろか、Timezoneの設定すらユーザー側では変更することができません。今回はそれをユーザーが自分で変更可能にするというお話です。
仕組み
LANGUAGEについて
基本となるコードはdjangoのマニュアルから抜粋これです。利用したい言語をtranslation.activateに渡し、クッキーに値をセットします。これでDjangoがこのセッション中で利用する言語のみを切り替えることができます。
from django.conf import settings
from django.http import HttpResponse
from django.utils import translation
user_language = 'fr'
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
今回はユーザーモデルに対して
TIMEZONEについて
基本となるコードはdjangoのマニュアルから抜粋これです。
利用したいタイムゾーンをtimezone.activateに渡します。
import pytz
from django.utils import timezone
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tzname = request.session.get('django_timezone')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
timezone.deactivate()
return self.get_response(request)
あとはこれらのコードを基礎として、ユーザーモデルをカスタマイズして言語設定とタイムゾーン設定を扱うフィールドを用意し、ユーザー自身がこれを変更可能にすることです。
というわけで、今回用意したのがdjango-user-g11nです。
django-user-g11n
django-user-g11nは、Djangoのカスタムユーザーモデルを用意し、そこに "TimeZoneAndUserLanguageSupportMixin"を継承することでlanguageとtimezone、2つのフィールドをユーザーモデルに持たせ、Middlewareを用いて設定された言語やタイムゾーンを動的に変更します。
ちなみに、g11nとは(Globalization)を省略したもので、I18n+L10Nを指します。
使い方
の前に、もしサンプルをすぐに試したい方は"サンプルを利用する"を参照してください。
まずはDjangoとdjango-user-g11nをインストールします。
$ pip install django django-user-g11n
次にdjango-admin.pyを用いてプロジェクトをスタートします。
$ django-admin.py startproject example
次に、カスタムユーザーモデルを作成します。ここでは便宜上accountsというアプリケーション名にします。
$ manage.py startapp accounts
アプリを作り終えたら、accounts/models.pyを変更して、カスタムユーザーモデルを追加します。
その際に、UserLanguageSupportMixinとUserTimeZoneSupportMixinを継承します。
from django.contrib.auth import models as auth_models
from user_g11n.models import UserLanguageSupportMixin, UserTimeZoneSupportMixin
class User(UserTimeZoneSupportMixin,
UserLanguageSupportMixin,
auth_models.AbstractUser):
pass
次はsettings.pyの変更です。
settings.pyの変更
INSTALLED_APPSに今回作成したカスタムユーザーモデル用のアプリと、user-g11nを追加します。
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
.
.
.
'accounts', # Your Custom user model application
'user_g11n', # Add
)
次にMIDDLEWAREを変更し、UserLanguageMiddlewareとUserTimeZoneMiddlewareを追加します。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
.
.
.
'user_g11n.middleware.UserLanguageMiddleware', # Add
'user_g11n.middleware.UserTimeZoneMiddleware', # Add
]
そして、AUTH_USER_MODELを指定し、カスタムユーザーモデルを利用可能にします。
AUTH_USER_MODEL = 'accounts.User'
次にI18N, L10N, TZ、TIME_ZONEをそれぞれ設定します。
USE_I18N = True
USE_L10N = True
USE_TZ = True
TIME_ZONE = "Asia/Tokyo" # Change to your local timezone
変更が完了したらmigrationを行いDBを作成します。
$ ./manage.py makemigrations && ./manage.py migrate
これで完了です。あとはsuperuserを作成し、ユーザーの管理画面で言語、タイムゾーンの変更をおこなうことで、リアルタイムに表示が切り替わります。
サンプルを利用する
このDjangoアプリはGitHub上にて開発を行っています。
プロジェクトをCloneしてもらい、
$ docker-compose up
したあと、http://localhost:8000にアクセスすることで、サンプルアプリを試すことができます。
まとめ
- Djangoの国際化機構最高
- 最初からこれ用意してくれよって思う。なぜないのか…
- DjangoおぢさんはDjangoが大好き。
- 今年(2020)はCovid-19の影響でDjango Congressなくなっちゃったけど、俺らの心のなかでは開催されているのだ!毎日のように・・・!
2022/5 にUpdate
Django4 からpytzがtimezoneに置き換わることにより、動かなくなる事態が発生したのでUpdateしました。
対応したのは以下の通り
- Django3ならPytzを利用(timezoneは入れられれば使えるけど、面倒なので対応無し)
- Django4で
-
USE_DEPRECATED_PYTZ
がFalseならばtimezoneを利用 -
USE_DEPRECATED_PYTZ
がTrueならばpytzを利用
-
以上