現在、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 |
今回使用した、コードに関しては、参考サイトの方にほぼすべてあります。というか、掲載されているコードそのままで動きました。
■参考サイト