~ubuntu-branches/ubuntu/utopic/dogtail/utopic

« back to all changes in this revision

Viewing changes to dogtail/i18n.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Holbach
  • Date: 2006-12-21 13:33:47 UTC
  • mfrom: (1.2.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 5.
  • Revision ID: james.westby@ubuntu.com-20061221133347-xo9jg11afp5plcka
Tags: upstream-0.6.1
ImportĀ upstreamĀ versionĀ 0.6.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from logging import debugLogger as logger
18
18
 
 
19
def safeDecode(string):
 
20
    if not isinstance(string, unicode):
 
21
        try:
 
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')
 
27
    return string
 
28
 
 
29
def safeEncode(string):
 
30
    pass
 
31
 
 
32
 
19
33
"""
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.
22
36
"""
23
37
translationDbs = []
24
38
 
25
39
class TranslationDb:
26
 
        """
27
 
        Abstract base class representing a database of translations
28
 
        """
29
 
        def getTranslationsOf(self, srcName):
30
 
                """
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.
33
 
                
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 
36
 
                translate to both
37
 
                (i) "Faire suivre" for forwarding an email, and 
38
 
                (ii) "Suivant" for the next page in a wizard.
39
 
                """
40
 
                raise NotImplementedError
 
40
    """
 
41
    Abstract base class representing a database of translations
 
42
    """
 
43
    def getTranslationsOf(self, srcName):
 
44
        """
 
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.
 
47
 
 
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
 
50
        translate to both
 
51
        (i) "Faire suivre" for forwarding an email, and
 
52
        (ii) "Suivant" for the next page in a wizard.
 
53
        """
 
54
        raise NotImplementedError
41
55
 
42
56
class GettextTranslationDb(TranslationDb):
43
 
        """
44
 
        Implementation of TranslationDb which leverages gettext, using a single
45
 
        translation mo-file.
46
 
        """
47
 
        def __init__(self, moFile):
48
 
                self.__moFile = moFile
49
 
                self.__gnutranslations = gettext.GNUTranslations(open(moFile))
50
 
 
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:
56
 
                results = {}
57
 
                result = self.__gnutranslations.ugettext(srcName)
58
 
                if result!=srcName:
59
 
                        results[result]=None
60
 
 
61
 
                # Hack alert:
62
 
                #
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
67
 
                #
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. 
72
 
                # Ugly, but it works.
73
 
                        
74
 
                for index in range(len(srcName)):
75
 
                        candidate = srcName[:index]+"_"+srcName[index:]
76
 
                        result = self.__gnutranslations.ugettext(candidate)
77
 
                        if result!=candidate:
78
 
                                # Strip out the underscore, and add to the result:
79
 
                                results[result.replace('_','')]=True
80
 
                        
81
 
                return results.keys()
 
57
    """
 
58
    Implementation of TranslationDb which leverages gettext, using a single
 
59
    translation mo-file.
 
60
    """
 
61
    def __init__(self, moFile):
 
62
        self.__moFile = moFile
 
63
        self.__gnutranslations = gettext.GNUTranslations(open(moFile))
 
64
 
 
65
    def getTranslationsOf(self, srcName):
 
66
        srcName = safeDecode(srcName)
 
67
        # print "searching for translations of %s"%srcName
 
68
        # Use a dict to get uniqueness:
 
69
        results = {}
 
70
        result = self.__gnutranslations.ugettext(srcName)
 
71
        if result!=srcName:
 
72
            results[result]=None
 
73
 
 
74
        # Hack alert:
 
75
        #
 
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
 
80
        #
 
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.
 
85
        # Ugly, but it works.
 
86
 
 
87
        for index in range(len(srcName)):
 
88
            candidate = srcName[:index]+"_"+srcName[index:]
 
89
            result = self.__gnutranslations.ugettext(candidate)
 
90
            if result!=candidate:
 
91
                # Strip out the underscore, and add to the result:
 
92
                results[result.replace('_','')]=True
 
93
 
 
94
        return results.keys()
82
95
 
83
96
def translate(srcString):
84
 
        """
85
 
        Look up srcString in the various translation databases (if any), returning
86
 
        a list of all matches found (potentially the empty list)
87
 
        """
88
 
        # Use a dict to get uniqueness:
89
 
        results = {}
90
 
        # Try to translate the string:
91
 
        for translationDb in translationDbs:
92
 
                for result in translationDb.getTranslationsOf(srcString):
93
 
                        result = result.encode('utf-8')
94
 
                        results[result]=True
95
 
 
96
 
        # No translations found:
97
 
        if len(results)==0:
98
 
                if config.config.debugTranslation:
99
 
                        logger.log('Translation not found for "%s"'%srcString)
100
 
        return results.keys()
101
 
                
 
97
    """
 
98
    Look up srcString in the various translation databases (if any), returning
 
99
    a list of all matches found (potentially the empty list)
 
100
    """
 
101
    # Use a dict to get uniqueness:
 
102
    results = {}
 
103
    # Try to translate the string:
 
104
    for translationDb in translationDbs:
 
105
        for result in translationDb.getTranslationsOf(srcString):
 
106
            result = safeDecode(result)
 
107
            results[result]=True
 
108
 
 
109
    # No translations found:
 
110
    if len(results)==0:
 
111
        if config.config.debugTranslation:
 
112
            logger.log('Translation not found for "%s"'%srcString)
 
113
    return results.keys()
 
114
 
102
115
class TranslatableString:
103
 
        """
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.
106
 
        """ 
107
 
 
108
 
        def __init__(self, untranslatedString):
109
 
                """
110
 
                Constructor looks up the string in all of the translation databases, storing
111
 
                the various translations it finds.
112
 
                """
113
 
                if isinstance(untranslatedString, unicode):
114
 
                        untranslatedString = untranslatedString.encode('utf-8')
115
 
                else:
116
 
                        untranslatedString = untranslatedString.decode('utf-8')
117
 
                self.untranslatedString = untranslatedString
118
 
                self.translatedStrings = translate(untranslatedString)
119
 
 
120
 
        def matchedBy(self, string):
121
 
                """
122
 
                Compare the test string against either the translation of the original 
123
 
                string (or simply the original string, if no translation was found).
124
 
                """
125
 
                #print "comparing %s against %s"%(string, self)
126
 
                matched = False
127
 
                if len(self.translatedStrings)>0:
128
 
                        matched = string in self.translatedStrings
129
 
                        return matched
130
 
                else:
131
 
                        matched = string==self.untranslatedString
132
 
                        return matched
133
 
                        
134
 
        def __str__(self):
135
 
                """
136
 
                Provide a meaningful debug version of the string (and the translation in 
137
 
                use)
138
 
                """
139
 
                if len(self.translatedStrings)>0:
140
 
                        # build an output string, with commas in the correct places
141
 
                        translations = ""                       
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')
146
 
                else:
147
 
                        return '"%s"' % (self.untranslatedString)
 
116
    """
 
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.
 
119
    """
 
120
 
 
121
    def __init__(self, untranslatedString):
 
122
        """
 
123
        Constructor looks up the string in all of the translation databases, storing
 
124
        the various translations it finds.
 
125
        """
 
126
        if isinstance(untranslatedString, unicode):
 
127
            untranslatedString = safeDecode(untranslatedString)
 
128
        else:
 
129
            untranslatedString = safeDecode(untranslatedString)
 
130
        self.untranslatedString = untranslatedString
 
131
        self.translatedStrings = translate(untranslatedString)
 
132
 
 
133
    def matchedBy(self, string):
 
134
        """
 
135
        Compare the test string against either the translation of the original
 
136
        string (or simply the original string, if no translation was found).
 
137
        """
 
138
        #print "comparing %s against %s"%(string, self)
 
139
        def stringsMatch(inS, outS):
 
140
            """
 
141
            Compares a regular expression to a string
 
142
 
 
143
            inS: the regular expression (or normal string)
 
144
            outS: the normal string to be compared against
 
145
            """
 
146
            inString = str(inS)
 
147
            outString = outS
 
148
            if inString == outString:
 
149
                return True
 
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
 
159
            return matched
 
160
 
 
161
        matched = False
 
162
        # the 'ts' variable keeps track of whether we're working with
 
163
        # translated strings. it's only used for debugging purposes.
 
164
        #ts = 0
 
165
        #print string, str(self)
 
166
        for translatedString in self.translatedStrings:
 
167
            #ts = ts + 1
 
168
            matched = stringsMatch(translatedString, string)
 
169
            if not matched:
 
170
                matched = translatedString == string
 
171
            if matched: return matched
 
172
        #ts=0
 
173
        return stringsMatch(self.untranslatedString, string)
 
174
 
 
175
    def __str__(self):
 
176
        """
 
177
        Provide a meaningful debug version of the string (and the translation in
 
178
        use)
 
179
        """
 
180
        if len(self.translatedStrings)>0:
 
181
            # build an output string, with commas in the correct places
 
182
            translations = ""
 
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)
 
187
        else:
 
188
            return '"%s"' % (self.untranslatedString)
148
189
 
149
190
 
150
191
 
151
192
def isMoFile(filename, language = ''):
152
 
        """
153
 
        Does the given filename look like a gettext mo file?
154
 
        
155
 
        Optionally: Does the file also contain translations for a certain language,
156
 
        for example 'ja'?
157
 
        """
158
 
        if re.match('(.*)\\.mo$', filename):
159
 
                if not language: return True
160
 
                elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' % \
161
 
                                language, filename):
162
 
                        return True
163
 
                else:
164
 
                        return False
165
 
        else:
166
 
                return False
 
193
    """
 
194
    Does the given filename look like a gettext mo file?
 
195
 
 
196
    Optionally: Does the file also contain translations for a certain language,
 
197
    for example 'ja'?
 
198
    """
 
199
    if re.match('(.*)\\.mo$', filename):
 
200
        if not language: return True
 
201
        elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' % \
 
202
                        language, filename):
 
203
            return True
 
204
        else:
 
205
            return False
 
206
    else:
 
207
        return False
167
208
 
168
209
def getMoFilesForPackage(packageName, language = '', getDependencies=True):
169
 
        """
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'.
173
 
        """
174
 
        result = []
175
 
        for filename in distro.packageDb.getFiles(packageName):
176
 
                if isMoFile(filename, language):
177
 
                        result.append(filename)
178
 
 
179
 
        if getDependencies:
180
 
                # Recurse:
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))
185
 
                
186
 
        return result
 
210
    """
 
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'.
 
214
    """
 
215
    result = []
 
216
    for filename in distro.packageDb.getFiles(packageName):
 
217
        if isMoFile(filename, language):
 
218
            result.append(filename)
 
219
 
 
220
    if getDependencies:
 
221
        # Recurse:
 
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))
 
226
 
 
227
    return result
187
228
 
188
229
def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
189
 
        """
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.
192
 
        """
193
 
        # Keep a list of mo-files that are already in use to avoid duplicates.
194
 
        moFiles = {}
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:
201
 
                                try:
202
 
                                        translationDbs.append(GettextTranslationDb(moFile))
203
 
                                        moFiles[moFile] = None
204
 
                                except (AttributeError, IndexError), inst:
205
 
                                        if config.config.debugTranslation:
206
 
                                                #import traceback
207
 
                                                #logger.log(traceback.format_exc())
208
 
                                                logger.log("Warning: Failed to load mo-file for translation: " + moFile)
209
 
                                
210
 
        # Hack alert:
211
 
        #
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.
216
 
 
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)
221
 
 
 
230
    """
 
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.
 
233
    """
 
234
    # Keep a list of mo-files that are already in use to avoid duplicates.
 
235
    moFiles = {}
 
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:
 
242
                try:
 
243
                    translationDbs.append(GettextTranslationDb(moFile))
 
244
                    moFiles[moFile] = None
 
245
                except (AttributeError, IndexError), inst:
 
246
                    if config.config.debugTranslation:
 
247
                        #import traceback
 
248
                        #logger.log(traceback.format_exc())
 
249
                        logger.log("Warning: Failed to load mo-file for translation: " + moFile)
 
250
 
 
251
    # Hack alert:
 
252
    #
 
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.
 
257
 
 
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)