Djangoで言語の切り替え機能を実装する方法

スポンサードリンク



こんにちは。sinyです。

この記事ではPyCon US 2020 Onlineで発表されたDjangoの言語翻訳に関する情報をベースにノウハウをまとめました。

セッション動画は英語ですがYotubeで公開されています。

 

※記載間違いなどありましたらご指摘いただけると幸いです。

Djangoにおける言語翻訳

Djangoでは翻訳フレームワークを使って簡単に翻訳機能を実装することができます。

翻訳機能の実装手順は以下の通りです。

 

  1. アプリケーション設定
  2. ルーティング設定
  3.  翻訳用の文字列を作る
  4. 言語ファイルをコンパイルする

 

この中で3番目の翻訳用の文字列を作成する部分が一番大変な部分です。
その理由は、アプリケーション内のすべての表示可能な文字列を翻訳対象としてマーキングする必要があるためです。

トークンまたは文字列という用語は、アプリケーションで翻訳されるテキストの文字列を指します。
この記事内で扱っている翻訳という用語は、ある言語から別の言語へ文字列のマッピングを意味しています。

まとめると以下の通りです。

i18n とl10nについて

ここで、i18n とl10nについて補足しておきます。

i18nは多言語化対応(Internationalization)で、l10nは地域にあわせて最適化(Localization)するというイメージで理解しておけばよいと思います。

ちなみに、M17Nというのがありますが、これは Mulitilingalizationでソフトウェアを複数の言語を同時に扱えるようにすることです。

例えば、Webブラウザーやエディターなどがその例で、複数の言語が混在したコンテンツも扱えるようにしたものです。

なお、Djangoの翻訳機能実装手順において、InternationalizationとLocalizationは以下のような関係になっています。

internationalization
  1. 翻訳のためのアプリケーション設定
  2. 翻訳のためのルーティング設定
  3. 翻訳用の文字列を作る
Localization

4.言語ファイルをコンパイルする

 

internationalizationはdjangoフレームワークがツールとして処理してくれる部分です。

Djangoの翻訳機能の実装手順

以下の4つの詳細手順について解説します。

    1. アプリケーション設定
    2. ルーティング設定
    3.  翻訳用の文字列を作る
    4. 言語ファイルをコンパイルする

    アプリケーションの設定

     

    ここではsettings.pyに各種必要な設定を行っていきます。

    MIDDLEWAREの設定

     

    settings.pyのMIDDLEWARE'django.middleware.locale.LocaleMiddleware'を追加します。
    この設定は必須ではありませんが、言語設定を自動で検出してくれるため非常に便利です。

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.locale.LocaleMiddleware',  #追加
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]

     

    なお、LocaleMiddlewareは以下の順で言語設定をチェックします。

    LocaleMiddlewareの言語チェック順序
    1. URL language prefix
    2. Language cookie(LANGUAGE_COOKIE_NAME)
    3. Requestの中のlanguage http headerを参照する。
    4. 上記3つが見つからない場合language code(LANGUAGE_CODE)を参照。

    LocaleMiddlewareの順番は非常に重要なので以下の点に注意しましょう。

     
    • session middlewareの後にLocaleMiddlewareを設定する必要がある。
       →Localemiddelwaresessionを利用するため。

    • LocalemiddlewareCommon.middlewareの前に設定する必要がある。
      LocalemiddelwareURL prefixesを利用するため。

    LANGUAGE_CODEの設定

     

    次にsettings.pyのLANGUAGE_CODEを設定します。
    LANGUAGE_CODEではDjangoのデフォルト言語を設定します。

    ここでは以下の通り日本語に設定しておきます。

    LANGUAGE_CODE = 'ja'

     

    翻訳とロケールの日付フォーマットの指定

     

    次に翻訳とロケールの日付フォーマットの指定を行います。
    通常であれば以下の通りTrueになっています。

    USE_I18N = True # enable translations
    USE_L10N = True # 日付フォーマット設定

     

    なお、翻訳機能を利用する必要がない場合は上記パラメータを双方ともFalseすると多少パフォーマンスの向上に寄与するとのことです。

    LANGUAGESの設定

     

    次にLANGUAGESパラメータの設定です。
    このパラメータを指定することで翻訳に使われる言語を限定することができます。
    以下は、英語と日本語だけに限定する場合の設定です。

    from django.utils.translation import ugettext_lazy as _
    LANGUAGES = [
        ('en', _('English')),
        ('ja', _('Japanese')),
    ]

     

    LOCALE_PATHSの設定

     

    次に言語ファイルのディレクトリパスの設定をLOCALE_PATHSで行います。
    このパラメータを設定しない場合、各アプリケーションのlocaleディレクトリを参照しにいきます。

    以下は、BASE_DIR(Djangoプロジェクト直下)にlocaleフォルダを定義しています。

    LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
    

     

    以上でsettings.py上の設定は完了です。

     

    ルーティング設定

    language prefixesは、ページをURLに直接ロードするために必要な言語を示すことができます。
    以下、具体例で説明します。

    一番上のURLは、ページのリソースパスに接頭辞として英語の言語コード(/en/)が付いていますが、これはページを英語でロードします。
    2つ目のURLは簡体字中国語をロードします。
    3つ目は言語prefixがないのでデフォルト言語(今回のケースでは日本語)がロードされます。

    prefix_default_language=Falseにすることでprefixが与えられない場合にデフォルトの言語設定が利用されるようになります。
    ルートのurls.pyに以下のような設定を追加します。

    from django.contrib import admin
    from django.urls import path
    from django.conf.urls import include, url
    from django.conf.urls.i18n import i18n_patterns
    
    
    urlpatterns = i18n_patterns(
        path('admin/', admin.site.urls),
        #path() ・・・・
        prefix_default_language=False   
    )

     

     

    翻訳用の文字列を作成する

    翻訳させたい文章は個別に指定(マーキング)する必要があります。
    マーキングはDjangoのテンプレートやmodel.py等で設定することができます。

    Djangoモデルでマーキングする場合

     

    モデル(models.py)上ではhelp_textやverbose_name等を対象にマーキングすることができます。

    モデルでマーキングするには以下のコードでgettext_lazyメソッドをインポートします。

    from django.utils.translation import gettext_lazy as _

    慣例で、インポートしたgettet_lazyにはエイリアスとしてアンダースコア「_」を使うようです。

     モデルのようなケースではgettext_lazyメソッドを使わないと正常に機能せずエラーになるようです。

     

    マーキングのやり方ですが、翻訳したい部分を_(****)のように指定します。

    以下は家計簿アプリモデル定義でのマーキング例です。

    from django.db import models
    from datetime import datetime
    from django.utils.translation import gettext_lazy as _  #add
    
    
    class Category(models.Model):
    
        class Meta:
            #カテゴリ
            verbose_name =_('category')
            verbose_name_plural = _("category")
          
    #カラム名の定義
        category_name = models.CharField(max_length=255,unique=True)
    
        def __str__(self):
            return self.category_name
    
    
    class Kakeibo(models.Model):                
    
        class Meta:
          
            verbose_name =_("Household account book")
            verbose_name_plural =_("Household account book")
    
        #カラムの定義
        date = models.DateField(_("date"),default=datetime.now)
        category = models.ForeignKey(Category, on_delete = models.PROTECT, verbose_name=_("category"))
        money = models.IntegerField(_("Amount of money"), help_text=_("The unit is Japanese yen"))
        quantity = models.IntegerField(verbose_name=_("quantity"),default=0)
        memo = models.CharField(verbose_name=_("memo"), max_length=500)
    
        def __str__(self):
            return self.memo
    
    
    

     

    ビュー等でマーキングする場合

     

    モデル以外でもview.py等でマーキングの設定ができますが、この場合はgettext_lazyの代わりにgettextメソッドを利用します。

    以下はビュー内でマーキングした場合のサンプルコードです。

    from django.http import HttpResponse
    from django.utils.translation import gettext as _
    
    def my_view(request):
        output = _("ようこそホームページへ")
        return HttpResponse(output)

     

    テンプレートでマーキングする場合

     

    テンプレートファイル(html)でマーキングする場合は、テンプレートタグtransを使います。

    htmlファイルの冒頭で{% load i18n %}を記載し、翻訳させたい箇所をtransタグで指定します。

    {% load i18n %}
    <h1>{% trans "文字列" %}</h1>

    例えば以下のような感じでtransタグを指定します。

    <th class="text-center">{% trans "date" %}</th>
    <th class="text-center">{% trans "category" %}</th>
    <th class="text-center">{% trans "Amount of money" %}</th>
    <th class="text-center">{% trans "quantity" %}</th>
    <th class="text-center">{% trans "total fee" %}</th>
    <th class="text-center">{% trans "memo" %}</th>
    <th class="text-center">{% trans "Update / Delete" %}</th>

     

    メッセージファイルの作成

    翻訳対象へのマーキング設定が完了したら以下のコマンドを実行してメッセージファイルを作成します。

    python manage.py makemessages -l <ロケール名>

    日本語の翻訳ファイルを作成する場合は以下のコードを実行します。
    このコマンドを実行するとマーキング対象になっている情報(modes.py, views.py, テンプレートファイル等)をすべてチェックしてメッセージファイルを生成してくれます。

    python manage.py makemessages -l ja

     

     言語コードとロケール名は異なる場合があるので注意が必要です。
    例えば、簡体字中国語を使用:言語コードは「zh-hans」ですが、ロケール名は「zh_Hans」です。

    コマンドを実行すると以下のディレクトリに.poファイルが生成されます。

    <プロジェクト>/locale/ja/LC_MESSAGES/django.po

    ちなみに、既にpoファイルが存在する場合は、新規で追加、修正したマーキング情報が反映されます。

    翻訳設定を行う

     

    先ほど生成された.poファイルを開いて、具体的な翻訳内容を定義していきます。

    今回生成された.poファイルは以下のようになります。

    #: .\kakeibo\models.py:10 .\kakeibo\models.py:11 .\kakeibo\models.py:29
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:38
    msgid "category"
    msgstr ""
    
    #: .\kakeibo\models.py:24 .\kakeibo\models.py:25
    msgid "Household account book"
    msgstr ""
    
    #: .\kakeibo\models.py:28 .\kakeibo\templates\kakeibo\kakeibo_list.html:37
    msgid "date"
    msgstr ""
    
    #: .\kakeibo\models.py:30 .\kakeibo\templates\kakeibo\kakeibo_list.html:39
    msgid "Amount of money"
    msgstr ""
    
    #: .\kakeibo\models.py:30
    msgid "The unit is Japanese yen"
    msgstr ""
    
    #: .\kakeibo\models.py:31 .\kakeibo\templates\kakeibo\kakeibo_list.html:40
    msgid "quantity"
    msgstr ""
    
    #: .\kakeibo\models.py:32 .\kakeibo\templates\kakeibo\kakeibo_list.html:42
    msgid "memo"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:15
    msgid "List display"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:17
    msgid "sign up"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:20
    msgid "pie chart"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:23
    msgid "line chart"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:29
    msgid "Household account book application"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:41
    msgid "total fee"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:43
    msgid "Update / Delete"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:63
    msgid "Update Page"
    msgstr ""
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:64
    msgid "Delete Page"
    msgstr ""
    
    #: .\tutorial\settings.py:125
    msgid "English"
    msgstr ""
    
    #: .\tutorial\settings.py:126
    msgid "Japanese"
    msgstr ""
    
    #: .\tutorial\urls.py:7
    msgid "My Index Title"
    msgstr ""
    
    #: .\tutorial\urls.py:8
    msgid "My Site Administration"
    msgstr ""
    
    #: .\tutorial\urls.py:9
    msgid "My Site Management"
    msgstr ""
    

     

    msgstrの部分に翻訳対象の文字列を設定していきます。

    今回は英語を日本語に翻訳させる前提なので以下のように設定しました。

    #: .\kakeibo\models.py:10 .\kakeibo\models.py:11 .\kakeibo\models.py:29
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:38
    msgid "category"
    msgstr "カテゴリ"
    
    #: .\kakeibo\models.py:24 .\kakeibo\models.py:25
    msgid "Household account book"
    msgstr "家計簿アプリ"
    
    #: .\kakeibo\models.py:28 .\kakeibo\templates\kakeibo\kakeibo_list.html:37
    msgid "date"
    msgstr "日付"
    
    #: .\kakeibo\models.py:30 .\kakeibo\templates\kakeibo\kakeibo_list.html:39
    msgid "Amount of money"
    msgstr "金額"
    
    #: .\kakeibo\models.py:30
    msgid "The unit is Japanese yen"
    msgstr "単位は日本円"
    
    #: .\kakeibo\models.py:31 .\kakeibo\templates\kakeibo\kakeibo_list.html:40
    msgid "quantity"
    msgstr "数量"
    
    #: .\kakeibo\models.py:32 .\kakeibo\templates\kakeibo\kakeibo_list.html:42
    msgid "memo"
    msgstr "メモ"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:15
    msgid "List display"
    msgstr "一覧表示"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:17
    msgid "sign up"
    msgstr "新規登録"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:20
    msgid "pie chart"
    msgstr "円グラフ"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:23
    msgid "line chart"
    msgstr "折れ線グラフ"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:29
    msgid "Household account book application"
    msgstr "家計簿アプリ"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:41
    msgid "total fee"
    msgstr "合計金額"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:43
    msgid "Update / Delete"
    msgstr "更新/削除"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:63
    msgid "Update Page"
    msgstr "更新ページ"
    
    #: .\kakeibo\templates\kakeibo\kakeibo_list.html:64
    msgid "Delete Page"
    msgstr "削除ページ"
    
    #: .\tutorial\settings.py:125
    msgid "English"
    msgstr "英語"
    
    #: .\tutorial\settings.py:126
    msgid "Japanese"
    msgstr "日本語"
    
    #: .\tutorial\urls.py:7
    msgid "My Index Title"
    msgstr "インデックスページ"
    
    #: .\tutorial\urls.py:8
    msgid "My Site Administration"
    msgstr "管理サイト"
    
    #: .\tutorial\urls.py:9
    msgid "My Site Management"
    msgstr "サイトマネージメント"
    

     

     

    言語ファイルをコンパイルする

     

    翻訳の設定が完了したら以下のコマンドを実行して言語ファイルをコンパイルします。

    python manage.py compilemessages

     

    コンパイルすると.poファイルを.moファイルにコンバートしてDjangoが使えるようにしてくれます。

    なお、言語ファイルは、他の静的ファイル(.html、css、js、pngなど)と同様に扱う必要があります。

    以上で翻訳の設定は完了です。

     

    URLによって言語を切り替える

     

    アクセスするURLを変えるだけで言語を切り替えることができます。
    今回のケースだと以下のように言語を切り替えることができます。

    ①Prefixを指定しない場合 →デフォルトの言語(日本語)で表示される。
     
     http://127.0.0.1:8000/kakeibo/kakeibo_list/

    ①明示的にPrefixを指定する場合

    http://127.0.0.1:8000/ja/kakeibo/kakeibo_list/

    http://127.0.0.1:8000/en/kakeibo/kakeibo_list/

    adminサイトに言語スイッチボタンを追加する

     

    以下のようにadminサイトの右上に言語切り替えボタンのアイコンを表示させます。

    まず、<project>/<app>/templatetags/i18n_switcher.pyファイルを新規作成して以下のコードを記載します。

    from django import template
    from django.template.defaultfilters import stringfilter
    from django.conf import settings
     
    
    def switch_lang_code(path, language):
     
        # Get the supported language codes
        lang_codes = 
     
        # Validate the inputs
        if path == '':
            raise Exception('URL path for language switch is empty')
        elif path[0] != '/':
            raise Exception('URL path for language switch does not start with "/"')
        elif language not in lang_codes:
            raise Exception('%s is not a supported language code' % language)
     
        # Split the parts of the path
        parts = path.split('/')
     
        # Add or substitute the new language prefix
        if parts[1] in lang_codes:
            parts[1] = language
        else:
            parts[0] = "/" + language
     
        # Return the full new path
        return '/'.join(parts)
    
     
    register = template.Library()
     
    @register.filter
    @stringfilter
    def switch_i18n_prefix(path, language):
        """文字列パスを受け取る"""
        return switch_lang_code(path, language)
     
    @register.filter
    def switch_i18n(request, language):
        """リクエストオブジェクトを受け取り、そこからパスを取得"""
        return switch_lang_code(request.get_full_path(), language)

     

    adminサイト上に言語切り替え用ボタンを表示させたいので、カスタマイズしたadminテンプレートファイルを用意してadminテンプレートを上書きします。

    具体的には、<project>/templates/admin/base_site.htmlを新規作成して以下のコードを記載します。

    {% extends "admin/base_site.html" %}
    {% load i18n %}
    {% load staticfiles %}
    
    <!-- custom filter module -->
    {% load i18n_switcher %}
    
    {% block extrahead %}
        <link rel="shortcut icon" href="{% static 'images/favicon.ico' %}" />
        <link rel="stylesheet" type="text/css" href="{% static 'css/custom_admin.css' %}"/>
    {% endblock %}
    
    {% block userlinks %}
        <a href="{{ request|switch_i18n:'en' }}">
            <img class="i18n_flag" src="{% static 'images/flag-usa-16.png' %}"/>
        </a> /
        <a href="{{ request|switch_i18n:'ja' }}">
            <img class="i18n_flag" src="{% static 'images/flag-ja-16.png' %}"/>
        </a> /
        {% if user.is_active and user.is_staff %}
            {% url 'django-admindocs-docroot' as docsroot %}
            {% if docsroot %}
                <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
            {% endif %}
        {% endif %}
        {% if user.has_usable_password %}
            <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
        {% endif %}
        <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
    {% endblock %}

     

    ※settings.pyのTEMPALTESで'DIRS': [os.path.join(BASE_DIR, 'templates')]を設定している前提

    また以下のCSSファイルを用意しておきます。

    static/css/custom_admin.css

    .i18n_flag img {
        width: 16px;
        vertical-align: text-top;
    }

     

    static/images/配下に以下のような言語切り替え用のPNGファイルを用意しておきます。

     

    http://127.0.0.1:8000/adminにアクセスすると以下のように言語切り替えボタンが表示され、ボタンを押すと言語が切り替わるようになります。

     

    ボタンを押した際にエラーが発生する場合は、urls.pyで設定したprefix_default_languageの値をFalseに変更してみてください。

     

    言語切り替え用メニューの表示

     

    言語切り替えメニューの表示については以下のNaritoさんの記事をほぼそのまま活用させていただきました。

    まず、プロジェクト直下のurls.pyに以下の通り設定を追加します。

    urlpatterns = i18n_patterns(
        path('admin/', admin.site.urls),
        url('kakeibo/', include('kakeibo.urls')),
        path('i18n/', include('django.conf.urls.i18n')),  #add
        prefix_default_language=False
    )

     

    一覧表示画面のテンプレートファイルに以下のコードを追記します。

            <div class="btn-group float-right">
            <form action="{% url 'set_language' %}" method="post">{% csrf_token %}
              <input name="next" type="hidden" value="{{ redirect_to }}">
              <select name="language">
                  {% get_current_language as LANGUAGE_CODE %}
                  {% get_available_languages as LANGUAGES %}
                  {% get_language_info_list for LANGUAGES as languages %}
                  {% for language in languages %}
                      <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                          {{ language.name_local }} ({{ language.code }})
                      </option>
                  {% endfor %}
              </select>
              <input type="submit" value="Go">
          </form>
          </div>

     

    以上で設定完了です。

    一覧表示画面を更新すると以下のように画面右側に言語切り替え用のプルダウンメニューが表示されます。

    参考にしたサイト

     

     

     

    以上、「Djangoで言語翻訳機能を実装する方法」でした。

     

     

    おすすめの記事