LoginSignup
0
0

More than 5 years have passed since last update.

DRF: シリアライズデータの国際化(m17n)

Last updated at Posted at 2016-09-24

やりたいこと

  • 既存システムが日本語だけ対応
  • 端末からの言語指定で言語に応じたデータを返したい
  • 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'路線詳細)
  ... # その他のフィールド
  • namedescription の翻訳データが必要
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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0