Added i18n using gettext. Works only for program, not web sites.
This commit is contained in:
parent
c00bc5f046
commit
4311af06bb
|
@ -1,2 +1,3 @@
|
|||
*~
|
||||
*.pyc
|
||||
locale
|
||||
|
|
23
Makefile
23
Makefile
|
@ -1,6 +1,11 @@
|
|||
PROJECT_DIR=glasnost-xml
|
||||
DIST_FILE=glasnost-xml.tar.gz
|
||||
|
||||
PYTHON_VERSION=2.3
|
||||
|
||||
FIND=find
|
||||
PYGETTEXT=pygettext
|
||||
|
||||
dist-clean:
|
||||
|
||||
dist: dist-clean
|
||||
|
@ -15,3 +20,21 @@ dist: dist-clean
|
|||
tar czf ../$(DIST_FILE) $(PROJECT_DIR)
|
||||
rm -rf $(PROJECT_DIR)
|
||||
|
||||
mo: po/fr.po
|
||||
-mkdir -p locale/fr/LC_MESSAGES
|
||||
msgfmt --statistics -c -v -o locale/fr/LC_MESSAGES/glasnost-xml.mo po/fr.po
|
||||
for F in `$(FIND) . -type f -name '*.xml.in'`; \
|
||||
do \
|
||||
intltool-merge -x po/ $$F `dirname $$F`/`basename $$F .in`; \
|
||||
done
|
||||
|
||||
translations:
|
||||
-cp po/fr.po po/fr.pox
|
||||
for F in `$(FIND) . -type f -name '*.xml.in'`; \
|
||||
do \
|
||||
intltool-extract --type=gettext/xml $$F; \
|
||||
done
|
||||
$(PYGETTEXT) -d po/messages -k N_ \
|
||||
`$(FIND) . -type f -name "*.py"` \
|
||||
`$(FIND) . -type f -name "*.xml.in.h"`
|
||||
msgmerge -o po/fr.po -D po fr.pox messages.pot
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
__version__ = '$Revision$'[11:-2]
|
||||
|
||||
|
||||
applicationName = 'abracadabra-devel' # Changed on "make install".
|
||||
applicationName = 'glasnost-xml' # Changed on "make install".
|
||||
versionNumber = '(Unreleased CVS Version)' # Changed on "make install".
|
||||
|
||||
|
||||
|
@ -42,4 +42,3 @@ __builtin__.__dict__['_'] = lambda x: x
|
|||
import context
|
||||
context.clear()
|
||||
context.push()
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.pox
|
|
@ -0,0 +1,105 @@
|
|||
# Glasnost-XML
|
||||
# By: Frederic Peters <fpeters@entrouvert.be>
|
||||
# Emmanuel Raviart <eraviart@entrouvert.com>
|
||||
# Copyright (C) 2003 Entr'ouvert & Emmanuel Raviart
|
||||
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Glasnost-XML $Revision$\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: Sun Feb 8 16:54:53 2004\n"
|
||||
"PO-Revision-Date: 2004-02-07 16:37+0100\n"
|
||||
"Last-Translator: Emmanuel Raviart <eraviart@entrouvert.com>\n"
|
||||
"Language-Team: French <fr@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: abracadabra/dataholders.py:135
|
||||
msgid "Untitled Object #%s"
|
||||
msgstr "Objet sans titre #%s"
|
||||
|
||||
#: abracadabra/descriptions.py:169
|
||||
msgid ":"
|
||||
msgstr " :"
|
||||
|
||||
#: abracadabra/descriptions.py:653
|
||||
msgid "Date Selector"
|
||||
msgstr "Sélecteur de date"
|
||||
|
||||
#: abracadabra/descriptions.py:674
|
||||
msgid "Unknown Label"
|
||||
msgstr "Sans étiquette"
|
||||
|
||||
#: abracadabra/elections.py:56
|
||||
msgid "Unknown Subject"
|
||||
msgstr "Sans sujet"
|
||||
|
||||
#: abracadabra/groups.py:45
|
||||
msgid "Everybody"
|
||||
msgstr "Tout le monde"
|
||||
|
||||
#: abracadabra/http.py:185
|
||||
msgid "Alert"
|
||||
msgstr "Alerte"
|
||||
|
||||
#: abracadabra/http.py:192
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: abracadabra/identities.py:153
|
||||
msgid "Anonymous Identity #%s"
|
||||
msgstr "Identité anonyme #%s"
|
||||
|
||||
#: abracadabra/libertyalliance.py:292
|
||||
msgid "Liberty Alliance authentication response failed (reason = %s)."
|
||||
msgstr "La réponse a l'identification Liberty Alliance a échoué (raison = %s)."
|
||||
|
||||
#: abracadabra/libertyalliance.py:295 abracadabra/libertyalliance.py:425
|
||||
#: abracadabra/libertyalliance.py:514
|
||||
msgid "Failure"
|
||||
msgstr "Panne"
|
||||
|
||||
#: abracadabra/libertyalliance.py:421
|
||||
msgid "Identification creation request failed (reason = %s)."
|
||||
msgstr ""
|
||||
|
||||
#: abracadabra/libertyalliance.py:463
|
||||
msgid "Identification creation request succeeded."
|
||||
msgstr ""
|
||||
|
||||
#: abracadabra/libertyalliance.py:465
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: abracadabra/libertyalliance.py:510
|
||||
msgid "Liberty Alliance authentication failed on identity provider."
|
||||
msgstr ""
|
||||
|
||||
#: abracadabra/pagesequences.py:100
|
||||
msgid "Previous"
|
||||
msgstr ""
|
||||
|
||||
#: abracadabra/pagesequences.py:117
|
||||
msgid "Next"
|
||||
msgstr ""
|
||||
|
||||
#: server.py:400
|
||||
msgid "New %s"
|
||||
msgstr "Nouveau %s"
|
||||
|
||||
#~ msgid "Basic Functionalities"
|
||||
#~ msgstr "Fonctionalités de base"
|
|
@ -0,0 +1,90 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: Sun Feb 8 16:54:53 2004\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ./abracadabra/dataholders.py:135
|
||||
msgid "Untitled Object #%s"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/descriptions.py:169
|
||||
msgid ":"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/descriptions.py:653
|
||||
msgid "Date Selector"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/descriptions.py:674
|
||||
msgid "Unknown Label"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/elections.py:56
|
||||
msgid "Unknown Subject"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/groups.py:45
|
||||
msgid "Everybody"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/http.py:185
|
||||
msgid "Alert"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/http.py:192
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/identities.py:153
|
||||
msgid "Anonymous Identity #%s"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:292
|
||||
msgid "Liberty Alliance authentication response failed (reason = %s)."
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:295 ./abracadabra/libertyalliance.py:425
|
||||
#: ./abracadabra/libertyalliance.py:514
|
||||
msgid "Failure"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:421
|
||||
msgid "Identification creation request failed (reason = %s)."
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:463
|
||||
msgid "Identification creation request succeeded."
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:465
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/libertyalliance.py:510
|
||||
msgid "Liberty Alliance authentication failed on identity provider."
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/pagesequences.py:100
|
||||
msgid "Previous"
|
||||
msgstr ""
|
||||
|
||||
#: ./abracadabra/pagesequences.py:117
|
||||
msgid "Next"
|
||||
msgstr ""
|
||||
|
||||
#: ./server.py:400
|
||||
msgid "New %s"
|
||||
msgstr ""
|
||||
|
|
@ -27,14 +27,13 @@
|
|||
"""HTTP Server"""
|
||||
|
||||
|
||||
# TODO :
|
||||
# - Validate each XML file using its XML Schema.
|
||||
|
||||
|
||||
import __builtin__
|
||||
import cgi
|
||||
import Cookie
|
||||
import cStringIO
|
||||
import gettext
|
||||
import grp
|
||||
import locale
|
||||
import logging
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
|
@ -124,7 +123,6 @@ class Application(object):
|
|||
break
|
||||
else:
|
||||
virtualHost = virtualHosts[0]
|
||||
context.setVar('virtualHost', virtualHost)
|
||||
|
||||
context.push(
|
||||
_level = "handleHttpCommand",
|
||||
|
@ -139,10 +137,13 @@ class Application(object):
|
|||
fieldStorage = None,
|
||||
httpRequestHandler = httpRequestHandler,
|
||||
httpScriptDirectoryPath = None,
|
||||
languageSetInUrl = False,
|
||||
readLanguages = None,
|
||||
session = None,
|
||||
uriAuthority = httpRequestHandler.headers.get("Host"),
|
||||
uriAuthority = httpRequestHandler.headers["Host"],
|
||||
uriScheme = context.getVar("httpServer").uriScheme,
|
||||
user = None,
|
||||
virtualHost = virtualHost,
|
||||
virtualHostDirectoryPath = virtualHost.documentRoot,
|
||||
)
|
||||
try:
|
||||
|
@ -201,7 +202,7 @@ class Application(object):
|
|||
environ["CONTENT_TYPE"] = httpRequestHandler.headers.typeheader
|
||||
if not "content-type" in fakeHeaders:
|
||||
fakeHeaders["content-type"] = environ["CONTENT_TYPE"]
|
||||
length = httpRequestHandler.headers.getheader("content-length")
|
||||
length = httpRequestHandler.headers.get("content-length")
|
||||
if length:
|
||||
environ["CONTENT_LENGTH"] = length
|
||||
if httpQuery:
|
||||
|
@ -265,6 +266,25 @@ class Application(object):
|
|||
if user is not None:
|
||||
context.setVar("user", user)
|
||||
|
||||
# Look for the right languages to use for HTTP answer.
|
||||
languages = []
|
||||
if user is not None:
|
||||
userLanguage = user.language
|
||||
if userLanguage is not None:
|
||||
languages = [userLanguage]
|
||||
if not languages \
|
||||
and httpRequestHandler.headers.has_key('Accept-Language'):
|
||||
languages = [
|
||||
language.strip()[:2]
|
||||
for language in httpRequestHandler.headers[
|
||||
'Accept-Language'].split(',')
|
||||
if language.strip()]
|
||||
virtualHostLanguage = virtualHost.language
|
||||
if virtualHostLanguage not in languages:
|
||||
languages.append(virtualHostLanguage)
|
||||
context.setVar('readLanguages', languages)
|
||||
self.setGettextLanguage()
|
||||
|
||||
try:
|
||||
try:
|
||||
rootDataHolder.walk(
|
||||
|
@ -313,22 +333,48 @@ class Application(object):
|
|||
"yep", "http://abracadabra.entrouvert.org/0.0")
|
||||
xpaths.registerFunctions(self.xpathContext)
|
||||
|
||||
def setGettextLanguage(self):
|
||||
virtualHost = context.getVar('virtualHost')
|
||||
language = context.getVar('readLanguages')[0]
|
||||
try:
|
||||
locale.setlocale(locale.LC_COLLATE, (language, None))
|
||||
except locale.Error:
|
||||
locale.setlocale(locale.LC_COLLATE, 'C')
|
||||
domains = [abracadabra.applicationName] + (virtualHost.locales or [])
|
||||
localeDirectoryPath = 'locale' # FIXME: '/usr/share/locale'
|
||||
translation = gettext.NullTranslations()
|
||||
for domain in domains:
|
||||
try:
|
||||
domainTranslation = gettext.translation(
|
||||
domain, localeDirectoryPath, [language])
|
||||
except IOError:
|
||||
continue
|
||||
if not hasattr(domainTranslation, '_catalog'):
|
||||
continue
|
||||
if translation.__class__.__name__ == 'NullTranslations':
|
||||
translation = domainTranslation
|
||||
continue
|
||||
translation._catalog.update(domainTranslation._catalog)
|
||||
__builtin__.__dict__['_'] = translation.gettext
|
||||
|
||||
def stop(self):
|
||||
xpaths.unregisterFunctions(self.xpathContext)
|
||||
self.xpathContext.xpathFreeContext()
|
||||
del self.xpathContext
|
||||
|
||||
|
||||
class VirtualHost:
|
||||
errorDocuments = None
|
||||
serverAddress = None # (ip, port) tuple
|
||||
serverName = None
|
||||
class VirtualHost(object):
|
||||
documentRoot = None
|
||||
errorDocuments = None
|
||||
isSsl = False
|
||||
talkback = False
|
||||
language = 'en'
|
||||
locales = None
|
||||
serverAddress = None # (ip, port) tuple
|
||||
serverAdmin = None
|
||||
serverName = None
|
||||
SSLCertificateFile = None
|
||||
SSLCertificateKeyFile = None
|
||||
talkback = False
|
||||
|
||||
def __init__(self):
|
||||
self.errorDocuments = {}
|
||||
|
@ -474,7 +520,7 @@ if __name__ == "__main__":
|
|||
action = "store_false",
|
||||
default = True,
|
||||
help = "run main process in foreground, for process supervisors")
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.logLevel.upper() not in logging._levelNames.keys():
|
||||
|
@ -482,7 +528,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Configure root logger.
|
||||
logger = logging.getLogger()
|
||||
handler = logging.FileHandler("/tmp/glasnost-xml.log")
|
||||
handler = logging.FileHandler("/var/log/glasnost-xml.log")
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging._levelNames[options.logLevel.upper()])
|
||||
|
||||
|
|
|
@ -131,6 +131,16 @@ class Identity(dataholders.Xml):
|
|||
return None
|
||||
return Identifications(nodes[0], dataHolder = self)
|
||||
|
||||
def getLanguage(self):
|
||||
nodes = self.evaluateXpath("yep:language")
|
||||
if nodes:
|
||||
return nodes[0].content
|
||||
personLocation = self.personLocation
|
||||
if not personLocation:
|
||||
return None
|
||||
person = dataholders.walkToLocation(personLocation)
|
||||
return person.language
|
||||
|
||||
def getPersonLocation(self):
|
||||
nodes = self.evaluateXpath("yep:person/@src")
|
||||
if not nodes:
|
||||
|
@ -201,6 +211,7 @@ class Identity(dataholders.Xml):
|
|||
tokenNode.setContent(voteToken)
|
||||
|
||||
identifications = property(getIdentifications, setIdentifications)
|
||||
language = property(getLanguage)
|
||||
personLocation = property(getPersonLocation, setPersonLocation)
|
||||
simpleLabel = property(getSimpleLabel)
|
||||
|
||||
|
|
|
@ -45,6 +45,12 @@ class Person(dataholders.Xml):
|
|||
return None
|
||||
return nodes[0].content
|
||||
|
||||
def getLanguage(self):
|
||||
nodes = self.evaluateXpath("yep:language")
|
||||
if not nodes:
|
||||
return None
|
||||
return nodes[0].content
|
||||
|
||||
def getSimpleLabel(self):
|
||||
return self.fullName
|
||||
|
||||
|
@ -68,6 +74,7 @@ class Person(dataholders.Xml):
|
|||
|
||||
authorizations = property(getAuthorizations, setAuthorizations)
|
||||
fullName = property(getFullName, setFullName)
|
||||
language = property(getLanguage)
|
||||
simpleLabel = property(getSimpleLabel)
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,17 @@
|
|||
<xforms:input ref="yep:person">
|
||||
<xforms:label>Person</xforms:label>
|
||||
</xforms:input>
|
||||
<xforms:select1 appearance="minimal" ref="yep:language">
|
||||
<xforms:label>Language</xforms:label>
|
||||
<xforms:item>
|
||||
<xforms:label>English</xforms:label>
|
||||
<xforms:value>en</xforms:value>
|
||||
</xforms:item>
|
||||
<xforms:item>
|
||||
<xforms:label>Français</xforms:label>
|
||||
<xforms:value>fr</xforms:value>
|
||||
</xforms:item>
|
||||
</xforms:select1>
|
||||
</xforms:group>
|
||||
</yep:body>
|
||||
</yep:page>
|
||||
|
|
|
@ -17,6 +17,17 @@
|
|||
<xforms:input ref="yep:email">
|
||||
<xforms:label>Courriel</xforms:label>
|
||||
</xforms:input>
|
||||
<xforms:select1 appearance="minimal" ref="yep:language">
|
||||
<xforms:label>Language</xforms:label>
|
||||
<xforms:item>
|
||||
<xforms:label>English</xforms:label>
|
||||
<xforms:value>en</xforms:value>
|
||||
</xforms:item>
|
||||
<xforms:item>
|
||||
<xforms:label>Français</xforms:label>
|
||||
<xforms:value>fr</xforms:value>
|
||||
</xforms:item>
|
||||
</xforms:select1>
|
||||
<xforms:select appearance="full" ref="yep:authorizations">
|
||||
<xforms:label>Caractéristiques</xforms:label>
|
||||
<xforms:item>
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="person" type="Person"/>
|
||||
<xsd:element name="language" type="xsd:language"/>
|
||||
</xsd:sequence>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<xsd:sequence>
|
||||
<xsd:element name="fullName" type="xsd:string"/>
|
||||
<xsd:element name="email" type="xsd:string"/>
|
||||
<xsd:element name="language" type="xsd:language"/>
|
||||
<xsd:element name="authorizations" type="xsd:string"/>
|
||||
</xsd:sequence>
|
||||
</xsd:extension>
|
||||
|
|
Reference in New Issue