865700c57a030d58bf1d1a146d233449872bfc75
[working/Evergreen.git] / build / i18n / scripts / db-seed-i18n.py
1 #!/usr/bin/env python
2 # vim:et:ts=4:sw=4:
3 """
4 This class enables translation of Evergreen's seed database strings.
5
6 Requires polib from http://polib.googlecode.com
7 """
8 # Copyright 2007-2008 Dan Scott <dscott@laurentian.ca>
9 #
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.
14 #
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.
19
20 import basel10n
21 import optparse
22 import polib
23 import re
24 import sys
25
26 class SQL(basel10n.BaseL10N):
27     """
28     This class provides methods for extracting translatable strings from
29     Evergreen's database seed values, generating a translatable POT file,
30     reading translated PO files, and generating SQL for inserting the
31     translated values into the Evergreen database.
32     """
33
34     def __init__(self):
35         self.pot = None
36         basel10n.BaseL10N.__init__(self)
37         self.sql = []
38
39     def getstrings(self, source):
40         """
41         Each INSERT statement contains 0 or more oils_i18n_gettext()
42         markers for the en-US string that identify the string (which
43         we push into the POEntry.occurrences attribute), class hint,
44         and property. We concatenate the class hint and property and
45         use that for our msgid attribute.
46         
47         A sample INSERT string that we'll scan is as follows:
48
49             INSERT INTO foo.bar (key, value) VALUES 
50                 (99, oils_i18n_gettext(99, 'string', 'class hint', 'property'));
51         """
52         self.pothead()
53
54         num = 0
55         findi18n = re.compile(r'.*?oils_i18n_gettext\((.*?)\'\)')
56         intkey = re.compile(r'\s*(?P<id>\d+),\s*\'(?P<string>.+?)\',\s*\'(?P<class>.+?)\',\s*\'(?P<property>.+?)$')
57         textkey = re.compile(r'\s*\'(?P<id>.*?)\',\s*\'(?P<string>.+?)\',\s*\'(?P<class>.+?)\',\s*\'(?P<property>.+?)$')
58         serts = dict()
59
60         # Iterate through the source SQL grabbing table names and l10n strings
61         sourcefile = open(source)
62         for line in sourcefile:
63             try:
64                 num = num + 1
65                 entry = findi18n.search(line)
66                 if entry is None:
67                     continue
68                 for parms in entry.groups():
69                     # Try for an integer-based primary key parameter first
70                     fi18n = intkey.search(parms)
71                     if fi18n is None:
72                         # Otherwise, it must be a text-based primary key parameter
73                         fi18n = textkey.search(parms)
74                     fq_field = "%s.%s" % (fi18n.group('class'), fi18n.group('property'))
75                     # Unescape escaped SQL single-quotes for translators' sanity
76                     msgid = re.compile(r'\'\'').sub("'", fi18n.group('string'))
77
78                     # Hmm, sometimes people use ":" in text identifiers and
79                     # polib doesn't seem to like that; urlencode the colon
80                     occurid = re.compile(r':').sub("%3A", fi18n.group('id'))
81
82                     if (msgid in serts):
83                         serts[msgid].occurrences.append((fq_field, occurid))
84                     else:
85                         poe = polib.POEntry()
86                         poe.occurrences = [(fq_field, occurid)]
87                         poe.msgid = msgid
88                         serts[msgid] = poe
89             except:
90                 print "Error in line %d of SQL source file" % (num) 
91
92         for poe in serts.values():
93             self.pot.append(poe)
94
95     def create_sql(self, locale):
96         """
97         Creates a set of INSERT statements that place translated strings
98         into the config.i18n_core table.
99         """
100
101         insert = "INSERT INTO config.i18n_core (fq_field, identity_value," \
102             " translation, string) VALUES ('%s', '%s', '%s', '%s');"
103         for entry in self.pot:
104             for fq_field in entry.occurrences:
105                 # Escape SQL single-quotes to avoid b0rkage
106                 msgstr = re.compile(r'\'').sub("''", entry.msgstr)
107
108                 # And unescape any colons in the occurence ID
109                 occurid = re.compile(r'%3A').sub(':', fq_field[1])
110
111                 if msgstr == '':
112                     # Don't generate a stmt for an untranslated string
113                     break
114                 self.sql.append(insert % (fq_field[0], occurid, locale, msgstr))
115
116 def main():
117     """
118     Determine what action to take
119     """
120     opts = optparse.OptionParser()
121     opts.add_option('-p', '--pot', action='store', \
122         help='Generate POT from the specified source SQL file', metavar='FILE')
123     opts.add_option('-s', '--sql', action='store', \
124         help='Generate SQL from the specified source POT file', metavar='FILE')
125     opts.add_option('-l', '--locale', \
126         help='Locale of the SQL file that will be generated')
127     opts.add_option('-o', '--output', dest='outfile', \
128         help='Write output to FILE (defaults to STDOUT)', metavar='FILE')
129     (options, args) = opts.parse_args()
130
131     if options.pot:
132         pot = SQL()
133         pot.getstrings(options.pot)
134         if options.outfile:
135             pot.savepot(options.outfile)
136         else:
137             sys.stdout.write(pot.pot.__str__())
138     elif options.sql:
139         if not options.locale:
140             opts.error('Must specify an output locale')
141         pot = SQL()
142         pot.loadpo(options.sql)
143         pot.create_sql(options.locale)
144         if not options.outfile:
145             outfile = sys.stdout
146         else:
147             outfile = open(options.outfile, 'w')
148         for insert in pot.sql: 
149             outfile.write(insert + "\n")
150     else:
151         opts.print_help()
152
153 if __name__ == '__main__':
154     main()