[GAE/P]国際化に対応してみました 〜その1〜


現在、GAE/P環境にて作成しているアプリの国際化を対応してみました。Django-1.0やKayといったFWを使って国際化も対応できるようですが、今更変更するのもだるかったので、標準の環境で対応しました。

多分、次に作成するなら、標準の環境ではなく、他のFWを試すかもしれません。

とまぁ、自分のことは置いといて早速、国際化の手順を書いていきたいと思います。まずは、最終的な構成を書きます。

.
`-- src
    |-- app.yaml
    |-- conf
    |   |-- locale
    |   |   |-- en
    |   |   |   `-- LC_MESSAGES
    |   |   |       |-- django.mo
    |   |   |       `-- django.po
    |   |   `-- ja
    |   |       `-- LC_MESSAGES
    |   |           |-- django.mo
    |   |           `-- django.po
    |   `-- settings.py
    `-- org
        `-- fukata
            `-- mapshare
                |-- public
                |   |-- css
                |   |-- img
                |   `-- js
                `-- system
                    |-- handler
                    |   |-- i18NRequestHandler.py
                    |   |-- pc
                    |   |   `-- index.py
                    |   `-- webapi
                    |-- model
                    |-- template
                    |   `-- pc
                    `-- utils
                         `-- cookies.py

今回、国際化の為に新規に追加のは以下のファイル群都なります。

  • config/settings.py
  • config/locale/*
  • org/fukata/mapshare/system/i18NRequestHandler.py
  • org/fukata/mapshare/system/utils/cookies.py

次に各ファイルの内容になります。言語ファイル(.po, .mo)に関しては省きたいと思います。

■config/settings.py

1
2
3
4
5
6
7
8
9
10
11
 USE_I18N = True
 
 # Valid languages
 LANGUAGES = (
     # 'en', 'zh_TW' should match the directories in conf/locale/*
     ('en', _('English')),
     ('ja', _('Japanese')),
     )
 
 # This is a default language
 LANGUAGE_CODE = 'ja'

■org/fukata/mapshare/system/i18NRequestHandler.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 from django.utils import translation
 from google.appengine.ext import webapp
 from org.fukata.mapshare.system.utils.cookies import Cookies
 import os
 
 os.environ['DJANGO_SETTINGS_MODULE'] = 'conf.settings'
 from django.conf import settings
 # Force Django to reload settings
 settings._target = None
 
 class I18NRequestHandler(webapp.RequestHandler):
 
     def initialize(self, request, response):
         webapp.RequestHandler.initialize(self, request, response)
 
         self.request.COOKIES = Cookies(self)
         self.request.META = os.environ
         self.reset_language()
 
     def reset_language(self):
 
         # Decide the language from Cookies/Headers
         language = translation.get_language_from_request(self.request)
         translation.activate(language)
         self.request.LANGUAGE_CODE = translation.get_language()
 
         # Set headers in response
         self.response.headers['Content-Language'] = translation.get_language()
 #        translation.deactivate()

■org/fukata/mapshare/system/utils/cookies.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
 import UserDict
 from Cookie import BaseCookie
 class Cookies(UserDict.DictMixin):
     def __init__(self, handler, **policy):
         self.response = handler.response
         self._in = handler.request.cookies
         self.policy = policy
         if 'secure' not in policy and handler.request.environ.get('HTTPS', '').lower() in ['on', 'true']:
             policy['secure'] = True
         self._out = {}
     def __getitem__(self, key):
         if key in self._out:
             return self._out[key]
         if key in self._in:
             return self._in[key]
         raise KeyError(key)
     def __setitem__(self, key, item):
         self._out[key] = item
         self.set_cookie(key, item, **self.policy)
     def __contains__(self, key):
         return key in self._in or key in self._out
     def keys(self):
         return self._in.keys() + self._out.keys()
     def __delitem__(self, key):
         if key in self._out:
             del self._out[key]
             self.unset_cookie(key)
         if key in self._in:
             del self._in[key]
             p = {}
             if 'path' in self.policy: p['path'] = self.policy['path']
             if 'domain' in self.policy: p['domain'] = self.policy['domain']
             self.delete_cookie(key, **p)
     #begin WebOb functions
     def set_cookie(self, key, value='', max_age=None,
                    path='/', domain=None, secure=None, httponly=False,
                    version=None, comment=None):
         """
         Set (add) a cookie for the response
         """
         cookies = BaseCookie()
         cookies[key] = value
         for var_name, var_value in [
             ('max-age', max_age),
             ('path', path),
             ('domain', domain),
             ('secure', secure),
             ('HttpOnly', httponly),
             ('version', version),
             ('comment', comment),
             ]:
             if var_value is not None and var_value is not False:
                 cookies[key][var_name] = str(var_value)
             if max_age is not None:
                 cookies[key]['expires'] = max_age
         header_value = cookies[key].output(header='').lstrip()
         self.response.headers._headers.append(('Set-Cookie', header_value))
     def delete_cookie(self, key, path='/', domain=None):
         """
         Delete a cookie from the client.  Note that path and domain must match
         how the cookie was originally set.
         This sets the cookie to the empty string, and max_age=0 so
         that it should expire immediately.
         """
         self.set_cookie(key, '', path=path, domain=domain,
                         max_age=0)
     def unset_cookie(self, key):
         """
         Unset a cookie with the given name (remove it from the
         response).  If there are multiple cookies (e.g., two cookies
         with the same name and different paths or domains), all such
         cookies will be deleted.
         """
         existing = self.response.headers.get_all('Set-Cookie')
         if not existing:
             raise KeyError(
                 "No cookies at all have been set")
         del self.response.headers['Set-Cookie']
         found = False
         for header in existing:
             cookies = BaseCookie()
             cookies.load(header)
             if key in cookies:
                 found = True
                 del cookies[key]
             header = cookies.output(header='').lstrip()
             if header:
                 self.response.headers.add('Set-Cookie', header)
         if not found:
             raise KeyError(
                 "No cookie has been set with the name %r" % key)

となります。で、通常のハンドラに対して「webapp.RequestHandler」ではなく、「I18NRequestHandler」を継承するようにします。これで、基本的には大丈夫です。テンプレート内で、国際化のタグを使用するには、テンプレートの先頭などで、{% load i18n %}と記述します。これで、国際化用のタグが使えるようになります。

また、.poファイルに関してですが、sdkにスクリプトが付随しているようですが、自分は下記のように.moファイルを生成しています。まぁ、スクリプトを使った方が便利だと思います^^;

1
2
msgfmt django.po 
mv messages.mo  django.mo

今回使用した、コードに関しては、参考サイトの方にほぼすべてあります。というか、掲載されているコードそのままで動きました。

■参考サイト

関連記事