17
17
from logging import debugLogger as logger
19
def safeDecode(string):
20
if not isinstance(string, unicode):
22
string = string.decode('utf-8')
23
except UnicodeDecodeError:
24
#logger.log(traceback.format_exc())
25
#logger.log("The following string is invalid and caused the above error: '%s'" % string)
26
string = string.decode('utf-8', 'replace')
29
def safeEncode(string):
20
Singleton list of TranslationDb instances, to be initialized by the script with
34
Singleton list of TranslationDb instances, to be initialized by the script with
21
35
whatever translation databases it wants.
23
37
translationDbs = []
25
39
class TranslationDb:
27
Abstract base class representing a database of translations
29
def getTranslationsOf(self, srcName):
31
Pure virtual method to look up the translation of a string.
32
Returns a list of candidate strings (the translation), empty if not found.
34
Note that a source string can map to multiple translated strings. For
35
example, in the French translation of Evolution, the string "Forward" can
37
(i) "Faire suivre" for forwarding an email, and
38
(ii) "Suivant" for the next page in a wizard.
40
raise NotImplementedError
41
Abstract base class representing a database of translations
43
def getTranslationsOf(self, srcName):
45
Pure virtual method to look up the translation of a string.
46
Returns a list of candidate strings (the translation), empty if not found.
48
Note that a source string can map to multiple translated strings. For
49
example, in the French translation of Evolution, the string "Forward" can
51
(i) "Faire suivre" for forwarding an email, and
52
(ii) "Suivant" for the next page in a wizard.
54
raise NotImplementedError
42
56
class GettextTranslationDb(TranslationDb):
44
Implementation of TranslationDb which leverages gettext, using a single
47
def __init__(self, moFile):
48
self.__moFile = moFile
49
self.__gnutranslations = gettext.GNUTranslations(open(moFile))
51
def getTranslationsOf(self, srcName):
52
if not isinstance(srcName, unicode):
53
srcName = srcName.decode('utf-8')
54
# print "searching for translations of %s"%srcName
55
# Use a dict to get uniqueness:
57
result = self.__gnutranslations.ugettext(srcName)
63
# Note that typical UI definition in GTK etc contains strings with
64
# underscores to denote accelerators.
65
# For example, the stock GTK "Add" item has text "_Add" which e.g.
66
# translates to "A_jouter" in French
68
# Since these underscores have been stripped out before we see these strings,
69
# we are looking for a translation of "Add" into "Ajouter" in this case, so
70
# we need to fake it, by looking up the string multiple times, with underscores
71
# inserted in all possible positions, stripping underscores out of the result.
74
for index in range(len(srcName)):
75
candidate = srcName[:index]+"_"+srcName[index:]
76
result = self.__gnutranslations.ugettext(candidate)
78
# Strip out the underscore, and add to the result:
79
results[result.replace('_','')]=True
58
Implementation of TranslationDb which leverages gettext, using a single
61
def __init__(self, moFile):
62
self.__moFile = moFile
63
self.__gnutranslations = gettext.GNUTranslations(open(moFile))
65
def getTranslationsOf(self, srcName):
66
srcName = safeDecode(srcName)
67
# print "searching for translations of %s"%srcName
68
# Use a dict to get uniqueness:
70
result = self.__gnutranslations.ugettext(srcName)
76
# Note that typical UI definition in GTK etc contains strings with
77
# underscores to denote accelerators.
78
# For example, the stock GTK "Add" item has text "_Add" which e.g.
79
# translates to "A_jouter" in French
81
# Since these underscores have been stripped out before we see these strings,
82
# we are looking for a translation of "Add" into "Ajouter" in this case, so
83
# we need to fake it, by looking up the string multiple times, with underscores
84
# inserted in all possible positions, stripping underscores out of the result.
87
for index in range(len(srcName)):
88
candidate = srcName[:index]+"_"+srcName[index:]
89
result = self.__gnutranslations.ugettext(candidate)
91
# Strip out the underscore, and add to the result:
92
results[result.replace('_','')]=True
83
96
def translate(srcString):
85
Look up srcString in the various translation databases (if any), returning
86
a list of all matches found (potentially the empty list)
88
# Use a dict to get uniqueness:
90
# Try to translate the string:
91
for translationDb in translationDbs:
92
for result in translationDb.getTranslationsOf(srcString):
93
result = result.encode('utf-8')
96
# No translations found:
98
if config.config.debugTranslation:
99
logger.log('Translation not found for "%s"'%srcString)
100
return results.keys()
98
Look up srcString in the various translation databases (if any), returning
99
a list of all matches found (potentially the empty list)
101
# Use a dict to get uniqueness:
103
# Try to translate the string:
104
for translationDb in translationDbs:
105
for result in translationDb.getTranslationsOf(srcString):
106
result = safeDecode(result)
109
# No translations found:
111
if config.config.debugTranslation:
112
logger.log('Translation not found for "%s"'%srcString)
113
return results.keys()
102
115
class TranslatableString:
104
Class representing a string that we want to match strings against, handling
105
translation for us, by looking it up once at construction time.
108
def __init__(self, untranslatedString):
110
Constructor looks up the string in all of the translation databases, storing
111
the various translations it finds.
113
if isinstance(untranslatedString, unicode):
114
untranslatedString = untranslatedString.encode('utf-8')
116
untranslatedString = untranslatedString.decode('utf-8')
117
self.untranslatedString = untranslatedString
118
self.translatedStrings = translate(untranslatedString)
120
def matchedBy(self, string):
122
Compare the test string against either the translation of the original
123
string (or simply the original string, if no translation was found).
125
#print "comparing %s against %s"%(string, self)
127
if len(self.translatedStrings)>0:
128
matched = string in self.translatedStrings
131
matched = string==self.untranslatedString
136
Provide a meaningful debug version of the string (and the translation in
139
if len(self.translatedStrings)>0:
140
# build an output string, with commas in the correct places
142
for tString in self.translatedStrings:
143
translations += '"%s", ' % tString.decode('utf-8')
144
result = '"%s" (%s)' % (self.untranslatedString, translations)
145
return result.encode('utf-8')
147
return '"%s"' % (self.untranslatedString)
117
Class representing a string that we want to match strings against, handling
118
translation for us, by looking it up once at construction time.
121
def __init__(self, untranslatedString):
123
Constructor looks up the string in all of the translation databases, storing
124
the various translations it finds.
126
if isinstance(untranslatedString, unicode):
127
untranslatedString = safeDecode(untranslatedString)
129
untranslatedString = safeDecode(untranslatedString)
130
self.untranslatedString = untranslatedString
131
self.translatedStrings = translate(untranslatedString)
133
def matchedBy(self, string):
135
Compare the test string against either the translation of the original
136
string (or simply the original string, if no translation was found).
138
#print "comparing %s against %s"%(string, self)
139
def stringsMatch(inS, outS):
141
Compares a regular expression to a string
143
inS: the regular expression (or normal string)
144
outS: the normal string to be compared against
148
if inString == outString:
150
inString = inString + '$'
151
inString = safeDecode(inString)
152
outString = safeDecode(outString)
153
if inString[0] == '*':
154
inString = "\\" + inString
155
# Escape all parentheses, since grouping will never be needed here
156
inString = re.sub('([\(\)])', r'\\\1', inString)
157
match = re.match(inString, outString)
158
matched = match is not None
162
# the 'ts' variable keeps track of whether we're working with
163
# translated strings. it's only used for debugging purposes.
165
#print string, str(self)
166
for translatedString in self.translatedStrings:
168
matched = stringsMatch(translatedString, string)
170
matched = translatedString == string
171
if matched: return matched
173
return stringsMatch(self.untranslatedString, string)
177
Provide a meaningful debug version of the string (and the translation in
180
if len(self.translatedStrings)>0:
181
# build an output string, with commas in the correct places
183
for tString in self.translatedStrings:
184
translations += u'"%s", ' % safeDecode(tString)
185
result = u'"%s" (%s)' % (safeDecode(self.untranslatedString), translations)
186
return safeDecode(result)
188
return '"%s"' % (self.untranslatedString)
151
192
def isMoFile(filename, language = ''):
153
Does the given filename look like a gettext mo file?
155
Optionally: Does the file also contain translations for a certain language,
158
if re.match('(.*)\\.mo$', filename):
159
if not language: return True
160
elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' % \
194
Does the given filename look like a gettext mo file?
196
Optionally: Does the file also contain translations for a certain language,
199
if re.match('(.*)\\.mo$', filename):
200
if not language: return True
201
elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' % \
168
209
def getMoFilesForPackage(packageName, language = '', getDependencies=True):
170
Look up the named package and find all gettext mo files within it and its
171
dependencies. It is possible to restrict the results to those of a certain
172
language, for example 'ja'.
175
for filename in distro.packageDb.getFiles(packageName):
176
if isMoFile(filename, language):
177
result.append(filename)
181
for dep in distro.packageDb.getDependencies(packageName):
182
# We pass False to the inner call because getDependencies has already
183
# walked the full tree
184
result.extend(getMoFilesForPackage(dep, language, False))
211
Look up the named package and find all gettext mo files within it and its
212
dependencies. It is possible to restrict the results to those of a certain
213
language, for example 'ja'.
216
for filename in distro.packageDb.getFiles(packageName):
217
if isMoFile(filename, language):
218
result.append(filename)
222
for dep in distro.packageDb.getDependencies(packageName):
223
# We pass False to the inner call because getDependencies has already
224
# walked the full tree
225
result.extend(getMoFilesForPackage(dep, language, False))
188
229
def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
190
Helper function which appends all of the gettext translation mo-files used by
191
the package (and its dependencies) to the translation database list.
193
# Keep a list of mo-files that are already in use to avoid duplicates.
195
def load(packageName, language = '', getDependencies = True):
196
for moFile in getMoFilesForPackage(packageName, language, getDependencies):
197
# Searching the popt mo-files for translations makes gettext bail out,
198
# so we ignore them here. This is
199
# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 .
200
if 'popt.mo' not in moFile:
202
translationDbs.append(GettextTranslationDb(moFile))
203
moFiles[moFile] = None
204
except (AttributeError, IndexError), inst:
205
if config.config.debugTranslation:
207
#logger.log(traceback.format_exc())
208
logger.log("Warning: Failed to load mo-file for translation: " + moFile)
212
# The following special-case is necessary for Ubuntu, since their
213
# translations are shipped in a single huge package. The downside to
214
# this special case, aside from the simple fact that there is one,
215
# is that it makes automatic translations much slower.
217
language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2]
218
if isinstance(distro.distro, distro.Ubuntu):
219
load('language-pack-gnome-%s' % language, language)
220
load(packageName, language, getDependencies)
231
Helper function which appends all of the gettext translation mo-files used by
232
the package (and its dependencies) to the translation database list.
234
# Keep a list of mo-files that are already in use to avoid duplicates.
236
def load(packageName, language = '', getDependencies = True):
237
for moFile in getMoFilesForPackage(packageName, language, getDependencies):
238
# Searching the popt mo-files for translations makes gettext bail out,
239
# so we ignore them here. This is
240
# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 .
241
if 'popt.mo' not in moFile:
243
translationDbs.append(GettextTranslationDb(moFile))
244
moFiles[moFile] = None
245
except (AttributeError, IndexError), inst:
246
if config.config.debugTranslation:
248
#logger.log(traceback.format_exc())
249
logger.log("Warning: Failed to load mo-file for translation: " + moFile)
253
# The following special-case is necessary for Ubuntu, since their
254
# translations are shipped in a single huge package. The downside to
255
# this special case, aside from the simple fact that there is one,
256
# is that it makes automatic translations much slower.
258
language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2]
259
if isinstance(distro.distro, distro.Ubuntu):
260
load('language-pack-gnome-%s' % language, language)
261
load(packageName, language, getDependencies)