やりたいこと
- 既存システムが日本語だけ対応
- 端末からの言語指定で言語に応じたデータを返したい
- APIが多いので、メタデータ/インターフェースを変更したくない
方針
- Langugeでサポート言語を管理する
- 既存モデル(Line[運行路線]とか)に対してLanguageごとの翻訳データモデル(LineTransとか)を定義する
- ビューの処理開始時にリクエストごとに言語を設定する
- 必要なモデルをシリアライザに渡す
- シリアライザの中で現在の言語の翻訳データがあればモデルのフィールドを書き換える
- シリアライズの結果を以前と同じメタデータで返す
Language
class Language(BaseModel):
code = models.CharField(u'言語コード')
label = models.CharField(u'言語ラベル')
既存モデルのフィールドを翻訳データで書き換えて返す
from django.utils import translation
class TransQuerySet(BaseQuerySet):
def for_lang(self, obj, lang=None):
lang = lang or translation.get_language() # 現在の言語
m17n = self.filter(original=obj, lang__code=lang).first() # 翻訳データ
if not m17n:
# 言語の翻訳がなかったらそのまま返す
return obj
for field in self.model._meta.fields: # 置き換える
name = field.name
if field.name not in ['original', 'lang']:
setattr(obj, name, getattr(m17n, name))
# DO NOT SAVE THIS OBJ
return obj
元モデルの翻訳データモデルを定義
-
original
: 元モデル(Lineとか) -
lang
: Language - 翻訳が必要なデータは
field_names
で渡す
def i18n(cls, *field_names):
''' cls: 元となるモデルクラス
field_names: 翻訳が必要なフィールド
'''
def __unicode__(self):
# 最初のフィールドが表示されるとする
return unicode(getattr(self, field_names[0], ''))
meta = type(
b'Meta', (object, ),
dict(
verbose_name="{} {}".format(
cls._meta.verbose_name, _('Translation')),
verbose_name_plural="{} {}".format(
cls._meta.verbose_name_plural, _('Translation')),
unique_together=(('original', 'lang',),), # 言語ごとに1つだけ
)
)
# 元モデルからフィールド名に対応するフィールドクラスをコピーする
fields = dict(
(name, cls._meta.get_field(name).clone()) for name in field_names)
fields['__module__'] = cls.__module__
fields['Meta'] = meta
fields['original'] = models.ForeignKey(
cls, verbose_name=cls._meta.verbose_name, related_name='trans')
fields['lang'] = models.ForeignKey(
Language, verbose_name=_('Language'))
fields['__unicode__'] = __unicode__
fields['objects'] = querysets.TransQuerySet.as_manager()
return type(cls._meta.object_name + b"Trans", (models.Model,), fields)
既存モデルの多国語化
class Line(models.Model):
name = models.CharField(u'路線名')
description = models.TextField(u'路線詳細)
... # その他のフィールド
-
name
とdescription
の翻訳データが必要
LineTrans = m17n_models.i18n(Line, 'name', 'description', )
Admin
- Line の編集の時に各言語の LineTrans を編集する
class LineTransAdminInline(admin.StackedInline):
model = models.LineTrans
extra = 0
class LineAdmin(admin.OSMGeoAdmin):
inlines = [LineTransAdminInline, ]
....
API
- 指定したエリアでサービスされている路線情報を返す
from django.utils import translation
@decorators.api_view(['POST', 'GET'])
@decorators.authentication_classes((authentication.TokenAuthentication,))
@decorators.permission_classes((permissions.IsAuthenticated,))
@permission_required('tourists.change_tourist')
def api_line_list(request, id):
profile = request.user.profile # ユーザーのProfile モデル
translation.activate(profile.language.code) # ユーザーごとの言語設定
area = Area.objects.get(id=id)
data = serializers.LineSerializer(area.line_set.all(), many=True).data
res = JSONResponse(data)
return res
シリアライザ
- 多言語対応シリアライザをサブクラス
- メタデータは多言語化する前と同じ
class LineSerializer(LangModelSerializer):
class Meta:
model = models.Line
exclude = ('enabled', 'created_at', 'updated_at', )
多言語対応シリアライザ
from rest_framework import serializers
from collections import OrderedDict
class LangModelSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
'''シリアライズデータを作る'''
# 翻訳データのリレーションがあったら現在の言語で置き換えたインスタンスにする
if hasattr(instance, 'trans'):
instance = instance.trans.for_lang(instance)
ret = OrderedDict()
fields = [field for field in self.fields.values()
if not field.write_only]
for field in fields:
try:
attribute = field.get_attribute(instance)
except:
continue
if attribute is not None:
represenation = field.to_representation(attribute)
if represenation is None:
# Do not seralize empty objects
continue
if isinstance(represenation, list) and not represenation:
# Do not serialize empty lists
continue
ret[field.field_name] = represenation
return ret