-
Notifications
You must be signed in to change notification settings - Fork 13
/
translationdomain.py
156 lines (136 loc) · 6.22 KB
/
translationdomain.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
##############################################################################
#
# Copyright (c) 2001-2008 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Global Translation Service for providing I18n to file-based code.
"""
import sys
import zope.component
from zope.i18nmessageid import Message
from zope.i18n import translate, interpolate
from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
from zope.i18n.interfaces import ITranslationDomain, INegotiator
from ._compat import _u
# The configuration should specify a list of fallback languages for the
# site. If a particular catalog for a negotiated language is not available,
# then the zcml specified order should be tried. If that fails, then as a
# last resort the languages in the following list are tried. If these fail
# too, then the msgid is returned.
#
# Note that these fallbacks are used only to find a catalog. If a particular
# message in a catalog is not translated, tough luck, you get the msgid.
LANGUAGE_FALLBACKS = ['en']
PY3 = sys.version_info[0] == 3
if PY3:
unicode = str
_EMPTY = _u("")
class TranslationDomain(SimpleTranslationDomain):
def __init__(self, domain, fallbacks=None):
self.domain = domain
# _catalogs maps (language, domain) to IMessageCatalog instances
self._catalogs = {}
# _data maps IMessageCatalog.getIdentifier() to IMessageCatalog
self._data = {}
# What languages to fallback to, if there is no catalog for the
# requested language (no fallback on individual messages)
if fallbacks is None:
fallbacks = LANGUAGE_FALLBACKS
self._fallbacks = fallbacks
def _registerMessageCatalog(self, language, catalog_name):
key = language
mc = self._catalogs.setdefault(key, [])
mc.append(catalog_name)
def addCatalog(self, catalog):
self._data[catalog.getIdentifier()] = catalog
self._registerMessageCatalog(catalog.language,
catalog.getIdentifier())
def setLanguageFallbacks(self, fallbacks=None):
if fallbacks is None:
fallbacks = LANGUAGE_FALLBACKS
self._fallbacks = fallbacks
def translate(self, msgid, mapping=None, context=None,
target_language=None, default=None):
"""See zope.i18n.interfaces.ITranslationDomain"""
# if the msgid is empty, let's save a lot of calculations and return
# an empty string.
if msgid == _EMPTY:
return _EMPTY
if target_language is None and context is not None:
langs = self._catalogs.keys()
# invoke local or global unnamed 'INegotiator' utilities
negotiator = zope.component.getUtility(INegotiator)
# try to determine target language from negotiator utility
target_language = negotiator.getLanguage(langs, context)
return self._recursive_translate(
msgid, mapping, target_language, default, context)
def _recursive_translate(self, msgid, mapping, target_language, default,
context, seen=None):
"""Recursively translate msg."""
# MessageID attributes override arguments
if isinstance(msgid, Message):
if msgid.domain != self.domain:
return translate(msgid, msgid.domain, mapping, context,
target_language, default)
default = msgid.default
mapping = msgid.mapping
# Recursively translate mappings, if they are translatable
if (mapping is not None
and Message in (type(m) for m in mapping.values())):
if seen is None:
seen = set()
seen.add(msgid)
mapping = mapping.copy()
for key, value in mapping.items():
if isinstance(value, Message):
# TODO Why isn't there an IMessage interface?
# https://bugs.launchpad.net/zope3/+bug/220122
if value in seen:
raise ValueError(
"Circular reference in mappings detected: %s" %
value)
mapping[key] = self._recursive_translate(
value, mapping, target_language,
default, context, seen)
if default is None:
default = unicode(msgid)
# Get the translation. Use the specified fallbacks if this fails
catalog_names = self._catalogs.get(target_language)
if catalog_names is None:
for language in self._fallbacks:
catalog_names = self._catalogs.get(language)
if catalog_names is not None:
break
text = default
if catalog_names:
if len(catalog_names) == 1:
# this is a slight optimization for the case when there is a
# single catalog. More importantly, it is extremely helpful
# when testing and the test language is used, because it
# allows the test language to get the default.
text = self._data[catalog_names[0]].queryMessage(
msgid, default)
else:
for name in catalog_names:
catalog = self._data[name]
s = catalog.queryMessage(msgid)
if s is not None:
text = s
break
# Now we need to do the interpolation
if text and mapping:
text = interpolate(text, mapping)
return text
def getCatalogsInfo(self):
return self._catalogs
def reloadCatalogs(self, catalogNames):
for catalogName in catalogNames:
self._data[catalogName].reload()