🌏

Radioボタンで表示言語を切替可能なサイトを作る【Django】

2022/02/24に公開

これは同じHTMLファイルです。
スクリーンショット
「こんにちは」→「Hello」に変わっている
Djangoには、HTMLファイル内の文字列を他の文字列と置き換える機能があります。
これを活用すると、上のように日本語版サイトと英語版サイトの切り替えを1つのHTMLファイルで実装することができます。

やりたいこと

ユーザーが初めてサイトを訪れたときに自動でメニューが出てきて、言語を選択できるようにしたいです。
ラジオボタンによる言語選択の実装例
今回の目標
言語選択を下の画像のようにselectフォーム(プルダウンメニュー)で実装する方法は以下のサイトで解説されていたのですが、
ドロップダウンによる言語選択の実装例

  • radioボタンによる言語選択
  • submitボタンを押さなくても、言語を選択するだけで更新
  • サイトに初めて訪れたときだけ言語選択メニューを表示

を実装する方法を日本語でまとめて解説している記事が見つからなかったので、今回解説しようと思います。

環境

  • Python 3.10
  • Django 4.0.2
  • Bootstrap5

手順0. はじめに

まずはHTMLの翻訳対象となるテキストを、{% trans "翻訳対象テキスト" %}というようにtransタグで囲みます。

+{% load i18n %}

 <!doctype HTML>
 <html lang="{{ LANGUAGE_CODE }}">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
+	<h1>{% trans 'こんにちは' %}</h1>
    </body>
 </html>

ちなみに、lang="{{ LANGUAGE_CODE }}でhtmlタグに現在の言語コードを渡すことができます。LANGUAGE_CODEはページを更新・移動しても引き継がれます。助かりますね。

手順1. 設定を追加する

これからtransタグを機能させるために、setting.pyへ設定を追加していきます。
頑張っていきましょう。

ミドルウェアの追加

MIDDLEWAREのSessionMiddlewareの後、CommonMiddlewareの前に'django.middleware.locale.LocaleMiddleware'を追加します。

setting.py
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',
]

翻訳言語を指定

表示する言語のリスト「LANGUAGES」を作成し、setting.pyへ追加します。
下の例では日本語、英語、中国語(簡体字)、韓国語、タイ語を登録しています。

setting.py
from django.utils.translation import gettext_lazy as _

LANGUAGES = [
    ('ja', _('Japanese')),
    ('en', _('English')),
    ('zh-hans', _('Chinese')),
    ('ko', _('Korean')),
    ('th', _('Thai')),
]

リストの各要素は言語コードと、サーバー上での名称で構成されます。
言語コードは以下のページを参考にするとよいでしょう。
https://www.asahi-net.or.jp/~ax2s-kmtn/ref/iso639.html

なぜタイ語かというと、同級生にタイから来た女の子がいて、その子も母国語で閲覧できるようにしたいと思ったからです。日本語→中国語や韓国語はよくあるけど、日本語→タイ語が用意されているサイトって少ないですよね。


URLに言語コードを追加

settings.pyと同じディレクトリにある方のurls.pyを以下のようにします。

urls.py
from django.urls import path, include
from django.contrib import admin
from django.conf.urls.i18n import i18n_patterns

urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += i18n_patterns(
    path('', include('App名.urls')),
)

そしてアプリフォルダの中のurls.pyに、次のようなpathを追加します。

App名/urls.py
 from django.urls import path, include

 urlpatterns = [
 	...,
+	path('i18n/', include('django.conf.urls.i18n')),
 ]

これによって、日本語版サイトのURLはhttp://example.com/ja/、英語版サイトのURLはhttp://example.com/en/というように個別にURLが割り当てられるようになりました。
それぞれのURLにアクセスすると、対応する言語でサイトが表示されます。

手順2. 翻訳ファイルを用意

transタグで囲まれたテキストと置き換わるテキストを用意します。
例えば{% trans 'こんにちは' %}を英語と中国語に変換できるようにするときは、「Hello」や「你好」というテキストを用意します。
それをこれからファイルにまとめて、サーバーに置きます。

翻訳ファイルの場所を指定

まず、その翻訳テキストのファイルを置くディレクトリのパスを、settings.pyに追加します。今回は、プロジェクトのルート直下のlocaleというディレクトリにしました。

settings.py
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

ディレクトリを作成する

以下の2つの場所にlocaleディレクトリを作成します。

  1. プロジェクトのルートディレクトリ
  2. アプリケーションフォルダ

翻訳ファイルを読み込むとき、もしもアプリフォルダ内のtempletes(下記だとHello.html)で{% trans %}を使っていたら、Djangoはそのアプリフォルダ内のlocaleディレクトリを参照します。

Project
├── App
│   ├── locale   ←Hello.htmlからの参照先
│   └── templetes
│       └── Hello.html
├── locale   ←notApp.htmlからの参照先
├── templetes
│   └── notApp.html
└── ...

一方、どのアプリにも属さないtempletes(notApp.html)で{% trans %}を使っていたら、settings.pyのLOCALE_PATHSで指定したディレクトリを参照します。

翻訳ファイルを作成

Djangoプロジェクトのルートディレクトリまで移動し、makemessagesコマンドを実行します。
-lオプションで英語、中国語など翻訳先となる言語の言語コードを指定します。
(英語=en, 中国語=zh_hans)

terminal
django-admin makemessages -l 言語コード

django-adminではなくmanage.pyを使っても同じことができます。

terminal
python manage.py makemessages -l 言語コード

複数の言語を指定するときは、このように並べます。

terminal
django-admin makemessages -l en -l zh_hans

するとlocaleディレクトリの中に各言語のディレクトリと、それぞれに対応するdjango.poが作られます。

locale
├── en
│   └── LC_MESSAGES
│       └── django.po
└── zh_hans
    └── LC_MESSAGES
        └── django.po

翻訳ファイルを編集

django.poを開くと、以下のような記述があるので

locale/en/LC_MESSAGES/django.po
#: App/templates/Hello.html:9
msgid "こんにちは"
msgstr ""

msgstrに翻訳済テキストを入力します。

locale/en/LC_MESSAGES/django.po
#: App/templates/Hello.html:9
msgid "こんにちは"
msgstr "Hello"

翻訳ファイルを更新

編集を進めていくうちに、{% trans 'こんにちは' %}をやっぱり{% trans 'こんばんは' %}に変更したいな~と思うことがあると思います。
そんなときに、HTMLの該当箇所を編集した後で以下のコマンドを実行することで、全ての言語の翻訳ファイルから古い翻訳文字列を削除し、新しい翻訳文字列を追加することができます。

terminal
django-admin makemessages -a

コンパイルする

django.poが完成しても、コンパイルしなければHTMLに反映されません!
次のコマンドですべてのdjango.poをコンパイルしてください。

terminal
django-admin compilemessages

各言語のディレクトリにdjango.moが作成されれば成功です。


これで翻訳テキストが表示できるようになりました。
http//127.0.0.1:8000/th/にアクセスしてみたところ、正常に「こんにちは」がタイ語のテキストに置換されています。
タイ語のこんにちはが表示された画像
タイ語のこんにちは(多分)

手順3. フォームを設置

それでは言語を切り替えられるようになったところで、いよいよHTMLに言語選択メニューを設置します。

radioボタンフォーム

これがradioボタンによる言語選択フォームのコードです。
Bootstrapを使っていますが、radioボタンを縦並びにしたかっただけなので無くても構いません。

<form action="{% url 'set_language' %}" method="post" id="radio-language-form">
    {% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <input name="language" type="hidden" value="" />
    {% get_current_language as LANGUAGE_CODE %}
    {% get_available_languages as LANGUAGES %}
    {% get_language_info_list for LANGUAGES as languages %}
    {% for language in languages %}
    <div class="form-check" name="language">
	<input class="form-check-input" type="radio" name="langRadios" id="Radio-{{ language.code }}"
	       value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} checked{% endif %}>
	<label class="form-check-label" for="Radio-{{ language.code }}">
	    {{ language.name_local }} ({{ language.code }})
	</label>
    </div>
    {% endfor %}
</form>

このコードでは

  1. LANGUAGE_CODEに現在の言語コードを代入
  2. languagesと名付けたリストに、settings.pyに登録したすべての言語を代入
    (今回は日本語、英語、中国語、韓国語、タイ語)
  3. for文でlanguagesから言語を1つずつ取り出し、radioボタンを作成
    言語コードを各ボタンのvalueとする.
    もし言語コードがLANGUAGE_CODEと一致するなら、chackedオプションをつける.

という処理を行っています。

手順4. Changeイベントの用意

選択した言語をサーバーにPOSTするために、javascriptでスクリプトを追加します。
記述を簡単にするためにjQueryを使用。

<script type="text/javascript">
$(function(){
    $( 'input[name="langRadios"]:radio' ).change( function() {
        const radioval = $(this).val();
        const form = $('#radio-language-form');
        form.find('input[name="language"]').val(radioval);
        form.submit();
    });
});
</script>
  1. 各radioボタンのChangeイベントを監視
  2. Changeが発生したときに、そのradioボタンのvalueをform内の隠しタグに渡す
  3. formのsubmitを行う

以上の処理によって、ボタンが押されただけで反映できるようにしました。

ちなみにradioではなくSelectタグで実装しているときは、Selectタグにonchange="submit(this.form)"というオプションを追加するだけで同じことができます。
radioボタンでも試してみましたが、valueがPOSTできず断念。


以上が、radioボタンによってサイトの言語を切り替える手順でした。
あとは最後の仕上げです!

手順5. 初回だけ表示させる

Cookieを使って、サイトに初めて訪れたときだけ言語切り替えメニューが表示されるようにします。

メニュー表示スクリプト

次のようなjavascriptを、メニューを表示したいHTMLに追加します。

function getCookieValue(key) {
    const find = document.cookie.split('; ').find(row => row.startsWith(key));
    if(find !== undefined){
        return find.split('=')[1];
    }
    return null;
}

これはcookieが存在するか確認して、存在したらそのcookieの値を返し、存在しなかったらnullを返す関数です。

$(window).on('load', function(){
    if(getCookieValue('visit') === null) {
        var myModal = new bootstrap.Modal($('#staticBackdrop'));
        myModal.show();
    }
});

これはHTMLが読み込まれたときに、'visit'という名前のcookieが存在しなかったら、id=staticBackdropのbootstrapのmodalコンポーネントを表示する関数です。


Modalメニュー

以下のページから、Static backdropのModalのコードをコピーして、HTMLに設置します。
https://getbootstrap.jp/docs/5.0/components/modal/#static-backdrop
そして次のように編集します。

  • <div class="modal-body">...</div>の中に、言語選択フォームを置く
  • <div class="modal-footer">に、id=VisitBtnと名付けたボタンを置く
    <div class="modal-footer">
	<button type="button" class="btn btn-primary text-end" 
		data-bs-dismiss="modal" id="VisitBtn">
	    Next
	</button>
    </div>

メニュー非表示スクリプト

modal-footerのボタンを押した後は、それ以降サイトを訪れても言語選択メニューが表示されないようにします。
次のようなjavascriptをHTMLに追加して完成です。

$('#VisitBtn').click( function() {
    document.cookie = 'visit=true; path=/;';
});

これはid=VisitBtnのボタンがクリックされたときに、'visit'というCookieを作る処理です。
'visit'の値は'true'としていますが、処理と関係ないので何でもいいです。

備考

今回は1度modal-footerのボタンを押さなければ、何度サイトを訪れても言語選択メニューが表示される仕様にしています。言語選択の押し間違いがあるかもしれないので。
1度radioボタンを押すだけで言語選択メニューが表示されなくなるようにするには、メニュー表示関数を次のようにします。

$(window).on('load', function(){
    if(getCookieValue('visit') === null) {
        var myModal = new bootstrap.Modal($('#staticBackdrop'));
        myModal.show();
+	document.cookie = 'visit=true; path=/;';
    }
});

おわり

以上の手順で言語選択メニューを実装することができます。
radioによる言語選択メニューのスクリーンショット
各国の「ようこそ」(多分)
アドバイスや意見等があればどしどし送ってください。
ここまで読んでいただきありがとうございました!

参考

翻訳 | Django ドキュメント | Django
teratail -【Django】プルダウンではなくaタグのリンクで言語を変えたい
Django makemessages not creating newly added languages

Discussion