4 This class enables translation of Evergreen's seed database strings.
6 Requires polib from http://polib.googlecode.com
8 # Copyright 2007-2008 Dan Scott <dscott@laurentian.ca>
10 # This program is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU General Public License
12 # as published by the Free Software Foundation; either version 2
13 # of the License, or (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
28 class SQL(basel10n.BaseL10N):
30 This class provides methods for extracting translatable strings from
31 Evergreen's database seed values, generating a translatable POT file,
32 reading translated PO files, and generating SQL for inserting the
33 translated values into the Evergreen database.
38 basel10n.BaseL10N.__init__(self)
41 def getstrings(self, source):
43 Each INSERT statement contains 0 or more oils_i18n_gettext()
44 markers for the en-US string that identify the string (which
45 we push into the POEntry.occurrences attribute), class hint,
46 and property. We concatenate the class hint and property and
47 use that for our msgid attribute.
49 A sample INSERT string that we'll scan is as follows:
51 INSERT INTO foo.bar (key, value) VALUES
52 (99, oils_i18n_gettext(99, 'string', 'class hint', 'property'));
57 findi18n = re.compile(r'oils_i18n_gettext\((.*?)\'\s*\)', re.UNICODE+re.MULTILINE+re.DOTALL)
58 intkey = re.compile(r'\s*(?P<id>\d+)\s*,\s*E?\'(?P<string>.+?)\',\s*\'(?P<class>.+?)\',\s*\'(?P<property>.+?)$', re.UNICODE+re.MULTILINE+re.DOTALL)
59 textkey = re.compile(r'\s*\'(?P<id>.*?)\'\s*,\s*E?\'(?P<string>.+?)\',\s*\'(?P<class>.+?)\',\s*\'(?P<property>.+?)$', re.UNICODE+re.MULTILINE+re.DOTALL)
62 # Iterate through the source SQL grabbing table names and l10n strings
63 sourcefile = codecs.open(source, encoding='utf-8')
64 sourcelines = sourcefile.read()
66 for match in findi18n.finditer(sourcelines):
67 parms = match.group(1)
68 num = sourcelines[:match.start()].count('\n') + 1 # ugh
70 # Try for an integer-based primary key parameter first
71 fi18n = intkey.search(parms)
73 # Otherwise, it must be a text-based primary key parameter
74 fi18n = textkey.search(parms)
76 raise Exception("Cannot parse the source. Empty strings in there?")
78 fq_field = "%s.%s" % (fi18n.group('class'), fi18n.group('property'))
80 # strip sql string concatenation
81 strx = re.sub(r'\'\s*\|\|\s*\'', '', fi18n.group('string'))
83 # Unescape escaped SQL single-quotes for translators' sanity
84 msgid = re.compile(r'\'\'').sub("'", strx)
86 # Hmm, sometimes people use ":" in text identifiers and
87 # polib doesn't seem to like that; urlencode the colon
88 occurid = re.compile(r':').sub("%3A", fi18n.group('id'))
91 serts[msgid].occurrences.append((os.path.basename(source), num))
92 serts[msgid].tcomment = ' '.join((serts[msgid].tcomment, 'id::%s__%s' % (fq_field, occurid)))
95 poe.tcomment = 'id::%s__%s' % (fq_field, occurid)
96 poe.occurrences = [(os.path.basename(source), num)]
99 except Exception, exc:
100 print "Error in oils_i18n_gettext line %d of SQL source file: %s" % (num, exc)
102 for poe in serts.values():
105 def create_sql(self, locale):
107 Creates a set of INSERT statements that place translated strings
108 into the config.i18n_core table.
111 insert = "INSERT INTO config.i18n_core (fq_field, identity_value," \
112 " translation, string) VALUES ('%s', '%s', '%s', '%s');"
113 idregex = re.compile(r'^id::(?P<class>.*?)__(?P<id>.*?)$')
114 for entry in self.pot:
115 for id_value in entry.tcomment.split():
116 # Escape SQL single-quotes to avoid b0rkage
117 msgstr = re.compile(r'\'').sub("''", entry.msgstr)
119 identifier = idregex.search(id_value)
120 if identifier is None:
122 # And unescape any colons in the occurence ID
123 occurid = re.compile(r'%3A').sub(':', identifier.group('id'))
126 # Don't generate a stmt for an untranslated string
128 self.sql.append(insert % (identifier.group('class'), occurid, locale, msgstr))
132 Determine what action to take
134 opts = optparse.OptionParser()
135 opts.add_option('-p', '--pot', action='store', \
136 help='Generate POT from the specified source SQL file', metavar='FILE')
137 opts.add_option('-s', '--sql', action='store', \
138 help='Generate SQL from the specified source POT file', metavar='FILE')
139 opts.add_option('-l', '--locale', \
140 help='Locale of the SQL file that will be generated')
141 opts.add_option('-o', '--output', dest='outfile', \
142 help='Write output to FILE (defaults to STDOUT)', metavar='FILE')
143 (options, args) = opts.parse_args()
147 pot.getstrings(options.pot)
149 pot.savepot(options.outfile)
151 sys.stdout.write(pot.pot.__str__())
153 if not options.locale:
154 opts.error('Must specify an output locale')
156 pot.loadpo(options.sql)
157 pot.create_sql(options.locale)
158 if not options.outfile:
161 outfile = codecs.open(options.outfile, encoding='utf-8', mode='w')
162 for insert in pot.sql:
163 outfile.write(insert + "\n")
167 if __name__ == '__main__':